1 | 'use strict';
|
2 | const path = require('path');
|
3 | const fs = require('fs');
|
4 | const os = require('os');
|
5 | const assert = require('assert');
|
6 | const url = require('url');
|
7 | const queryString = require('querystring');
|
8 | const mkdirp = require('mkdirp');
|
9 | const cloneDeep = require('lodash.clonedeep');
|
10 | const cloneDeepWith = require('lodash.clonedeepwith');
|
11 | const uniq = require('lodash.uniq');
|
12 | const get = require('lodash.get');
|
13 | const set = require('lodash.set');
|
14 | const has = require('lodash.has');
|
15 | const merge = require('lodash.merge');
|
16 | const install = require('./install');
|
17 | const glob = require('glob');
|
18 | const md5 = require('md5');
|
19 | const shell = require('shelljs');
|
20 | const utils = Object.assign({}, {
|
21 | _: get,
|
22 | has,
|
23 | get,
|
24 | set,
|
25 | merge,
|
26 | cloneDeep,
|
27 | cloneDeepWith,
|
28 | queryString,
|
29 | uniq,
|
30 | mkdirp
|
31 | }, install);
|
32 |
|
33 | utils.has = value => {
|
34 | return value !== undefined;
|
35 | };
|
36 |
|
37 | utils.isFunction = value => typeof value === 'function';
|
38 |
|
39 | utils.isObject = value => Object.prototype.toString.call(value) === '[object Object]';
|
40 |
|
41 | utils.isString = value => typeof value === 'string';
|
42 |
|
43 | utils.isBoolean = value => typeof value === 'boolean';
|
44 |
|
45 | utils.normalizePath = (filepath, baseDir) => path.isAbsolute(filepath) ? filepath : path.join(baseDir, filepath);
|
46 |
|
47 | utils.normalizeBuildPath = (buildPath, baseDir) => {
|
48 | return utils.normalizePath(buildPath, baseDir);
|
49 | };
|
50 |
|
51 | utils.isHttpOrHttps = strUrl => {
|
52 | return /^(https?:|\/\/)/.test(strUrl);
|
53 | };
|
54 |
|
55 | utils.normalizePublicPath = publicPath => {
|
56 | publicPath = `${publicPath.replace(/\/$/, '')}/`;
|
57 | if (!utils.isHttpOrHttps(publicPath) && !/^\//.test(publicPath)) {
|
58 | publicPath = `/${publicPath}`;
|
59 | }
|
60 | return publicPath;
|
61 | };
|
62 |
|
63 | utils.isTrue = value => value !== 'false' && (!!value || value === undefined);
|
64 |
|
65 | utils.mixin = (target, source) => {
|
66 | const mixinProperty = utils.isObject(source) ? Object.getOwnPropertyNames(source) : Object.getOwnPropertyNames(source.prototype);
|
67 | mixinProperty.forEach(property => {
|
68 | if (property !== 'constructor') {
|
69 | target[property] = source.prototype[property];
|
70 | }
|
71 | });
|
72 | };
|
73 |
|
74 | utils.joinPath = function() {
|
75 | return [].slice.call(arguments, 0).map((arg, index) => {
|
76 | let tempArg = arg.replace(/\/$/, '');
|
77 | if (index > 0) {
|
78 | tempArg = arg.replace(/^\//, '');
|
79 | }
|
80 | return tempArg;
|
81 | }).join('/');
|
82 | };
|
83 |
|
84 | utils.getOutputPath = config => {
|
85 | const { output = {}, buildPath } = config;
|
86 | if (output.path) {
|
87 | return output.path;
|
88 | }
|
89 | return buildPath;
|
90 | };
|
91 |
|
92 | utils.getOutputPublicPath = config => {
|
93 | const { output = {}, publicPath } = config;
|
94 | if (output.publicPath) {
|
95 | return output.publicPath.replace(/\/$/, '');
|
96 | }
|
97 | return publicPath.replace(/\/$/, '');
|
98 | };
|
99 |
|
100 | utils.getEntry = (config, type) => {
|
101 | const entry = config.entry;
|
102 | if (utils.isObject(entry) && (entry.loader || entry.include) || entry instanceof RegExp) {
|
103 | return utils.getCustomEntry(config, type);
|
104 | }
|
105 | return utils.getGlobEntry(config, type);
|
106 | };
|
107 |
|
108 | utils.getCustomEntry = (config, type) => {
|
109 | let entryArray = [];
|
110 | let entryLoader;
|
111 | let extMatch = '.js';
|
112 | const configEntry = config.entry;
|
113 | const entries = {};
|
114 | if (configEntry && configEntry.include) {
|
115 | entryLoader = type && configEntry.loader && configEntry.loader[type];
|
116 | if (entryLoader) {
|
117 | entryLoader = utils.normalizePath(entryLoader, config.baseDir);
|
118 | }
|
119 | const extMapping = { vue: '.vue', react: '.jsx', weex: '.vue' };
|
120 | extMatch = entryLoader ? configEntry.extMatch || extMapping[config.framework] : configEntry.extMatch;
|
121 | entryArray = Array.isArray(configEntry.include) ? configEntry.include : [configEntry.include];
|
122 | } else {
|
123 | entryArray.push(configEntry);
|
124 | }
|
125 | entryArray.forEach(entry => {
|
126 | if (utils.isString(entry)) {
|
127 | const filepath = utils.normalizePath(entry, config.baseDir);
|
128 | if (fs.statSync(filepath).isDirectory()) {
|
129 | const dirEntry = utils.walkFile(filepath, configEntry.exclude, extMatch, config.baseDir);
|
130 | Object.assign(entries, utils.createEntry(config, entryLoader, dirEntry, true));
|
131 | } else {
|
132 | const entryName = path.basename(filepath, path.extname(filepath));
|
133 | const singleEntry = {};
|
134 | singleEntry[entryName] = filepath;
|
135 | Object.assign(entries, utils.createEntry(config, entryLoader, singleEntry, true));
|
136 | }
|
137 | } else if (entry instanceof RegExp) {
|
138 | const dirEntry = utils.walkFile(entry, configEntry.exclude, extMatch, config.baseDir);
|
139 | Object.assign(entries, utils.createEntry(config, entryLoader, dirEntry, false));
|
140 | } else if (utils.isObject(entry)) {
|
141 | Object.assign(entries, utils.createEntry(config, entryLoader, entry, true));
|
142 | }
|
143 | });
|
144 | return entries;
|
145 | };
|
146 |
|
147 |
|
148 | utils.glob = (root, str) => {
|
149 | const result = str.match(/!\((.*)\)/);
|
150 | if (result && result.length) {
|
151 | const matchIgnore = result[0];
|
152 | const matchIgnoreKeys = result[1];
|
153 | const matchStr = str.replace(matchIgnore, '');
|
154 | const ignore = matchIgnoreKeys.split('|').map(key => {
|
155 | if (/\./.test(key)) {
|
156 | return `**/${key}`;
|
157 | }
|
158 | return `**/${key}/**`;
|
159 | });
|
160 | return glob.sync(matchStr, { root, ignore });
|
161 | }
|
162 | return glob.sync(str, { root });
|
163 | };
|
164 |
|
165 | utils.getGlobEntry = (config, type) => {
|
166 | const { entry, baseDir } = config;
|
167 |
|
168 | if (utils.isString(entry)) {
|
169 | const root = utils.getDirByRegex(entry);
|
170 | const files = utils.glob(root, entry);
|
171 | const entries = {};
|
172 | files.forEach(file => {
|
173 | const ext = path.extname(file);
|
174 | const entryName = path.posix.relative(root, file).replace(ext, '');
|
175 | entries[entryName] = utils.normalizePath(file, baseDir);
|
176 | });
|
177 | return entries;
|
178 | }
|
179 | if (utils.isObject(entry)) {
|
180 | Object.keys(entry).forEach(key => {
|
181 | entry[key] = utils.normalizePath(entry[key], baseDir);
|
182 | });
|
183 | return entry;
|
184 | }
|
185 | return {};
|
186 | };
|
187 |
|
188 | utils.createEntry = (config, entryLoader, entryConfig, isParseUrl) => {
|
189 | const entries = {};
|
190 | const baseDir = config.baseDir;
|
191 | Object.keys(entryConfig).forEach(entryName => {
|
192 | let targetFile = entryConfig[entryName];
|
193 | let useLoader = !!entryLoader;
|
194 | if (isParseUrl && utils.isString(targetFile)) {
|
195 | const fileInfo = url.parse(targetFile);
|
196 | const params = queryString.parse(fileInfo.query);
|
197 | useLoader = utils.isTrue(params.loader);
|
198 | targetFile = targetFile.split('?')[0];
|
199 | targetFile = path.normalize(targetFile);
|
200 | targetFile = utils.normalizePath(targetFile, baseDir);
|
201 | }
|
202 | if (entryLoader && useLoader && utils.isString(targetFile)) {
|
203 | entries[entryName] = ['babel-loader', entryLoader, targetFile].join('!');
|
204 | } else {
|
205 | entries[entryName] = targetFile;
|
206 | }
|
207 | });
|
208 | return entries;
|
209 | };
|
210 |
|
211 | utils.getDirByRegex = (regex, baseDir) => {
|
212 | const strRegex = String(regex).replace(/^\//, '').replace(/\/$/, '').replace(/\\/g, '');
|
213 | const entryDir = strRegex.split('\/').reduce((dir, item) => {
|
214 | if (/^[A-Za-z0-9]*$/.test(item)) {
|
215 | return dir ? `${dir}/${item}` : item;
|
216 | }
|
217 | return dir;
|
218 | }, '');
|
219 | assert(entryDir, `The regex ${strRegex} must begin with / + a letter or number`);
|
220 | return baseDir ? utils.normalizePath(entryDir, baseDir) : entryDir;
|
221 | };
|
222 |
|
223 | utils.walkFile = (dirs, excludeRegex, extMatch = '.js', baseDir) => {
|
224 | const entries = {};
|
225 | let entryDir = '';
|
226 | const walk = (dir, include, exclude) => {
|
227 | const dirList = fs.readdirSync(dir);
|
228 | dirList.forEach(item => {
|
229 | const filePath = path.posix.join(dir, item);
|
230 |
|
231 | if (fs.statSync(filePath).isDirectory()) {
|
232 | walk(filePath, include, exclude);
|
233 | } else if (include.length > 0 && utils.isMatch(include, filePath) && !utils.isMatch(exclude, filePath) || include.length === 0 && !utils.isMatch(exclude, filePath)) {
|
234 | if (filePath.endsWith(extMatch)) {
|
235 | const entryName = filePath.replace(entryDir, '').replace(/^\//, '').replace(extMatch, '');
|
236 | entries[entryName] = filePath;
|
237 | }
|
238 | }
|
239 | });
|
240 | };
|
241 |
|
242 | const includeRegex = [];
|
243 | if (dirs instanceof RegExp) {
|
244 | includeRegex.push(dirs);
|
245 | dirs = utils.getDirByRegex(dirs, baseDir);
|
246 | }
|
247 | dirs = Array.isArray(dirs) ? dirs : [dirs];
|
248 | dirs.forEach(dir => {
|
249 | if (fs.existsSync(dir)) {
|
250 | entryDir = dir;
|
251 | walk(dir, includeRegex, excludeRegex);
|
252 | }
|
253 | });
|
254 | return entries;
|
255 | };
|
256 |
|
257 | utils.isMatch = (regexArray, strMatch) => {
|
258 | if (!regexArray) {
|
259 | return false;
|
260 | }
|
261 |
|
262 | const normalizeStrMatch = strMatch.replace(/\\/g, '/').replace(/\/\//g, '/');
|
263 | regexArray = Array.isArray(regexArray) ? regexArray : [regexArray];
|
264 | return regexArray.some(item => new RegExp(item, '').test(normalizeStrMatch));
|
265 | };
|
266 |
|
267 | utils.assetsPath = (prefix, filepath) => path.posix.join(prefix, filepath);
|
268 |
|
269 | utils.getLoaderOptionString = (name, options) => {
|
270 | const kvArray = [];
|
271 | options && Object.keys(options).forEach(key => {
|
272 | const value = options[key];
|
273 | if (Array.isArray(value)) {
|
274 | value.forEach(item => {
|
275 | kvArray.push(`${key}[]=${item}`);
|
276 | });
|
277 | } else if (typeof value === 'object') {
|
278 |
|
279 | } else if (typeof value === 'boolean') {
|
280 | kvArray.push(value === true ? `+${key}` : `-${key}`);
|
281 | } else {
|
282 | kvArray.push(`${key}=${value}`);
|
283 | }
|
284 | });
|
285 | const optionStr = kvArray.join(',');
|
286 | if (name) {
|
287 | return /\?/.test(name) ? `${name},${optionStr}` : name + (optionStr ? `?${optionStr}` : '');
|
288 | }
|
289 | return optionStr;
|
290 | };
|
291 |
|
292 | utils.getLoaderLabel = (loader, ctx) => {
|
293 | let loaderName = loader;
|
294 | if (utils.isObject(loader)) {
|
295 | if (loader.name) {
|
296 | loaderName = loader.name;
|
297 | } else if (loader.loader) {
|
298 | loaderName = loader.loader;
|
299 | } else if (Array.isArray(loader.use)) {
|
300 |
|
301 | loaderName = loader.use.reduce((names, item) => {
|
302 | if (utils.isString(item)) {
|
303 | names.push(item.replace(/-loader$/, ''));
|
304 | } else if (item.loader) {
|
305 | names.push(item.loader.replace(/-loader$/, ''));
|
306 | }
|
307 | return names;
|
308 | }, []).join('-');
|
309 | } else if (Object.keys(loader).length === 1) {
|
310 | loaderName = Object.keys(loader)[0];
|
311 | }
|
312 | }
|
313 | return utils.isString(loaderName) && loaderName.replace(/-loader$/, '');
|
314 | };
|
315 |
|
316 | utils.loadNodeModules = isCache => {
|
317 | const nodeModules = {};
|
318 | const cacheFile = path.join(__dirname, '../temp/cache.json');
|
319 | if (isCache && fs.existsSync(cacheFile)) {
|
320 | return require(cacheFile);
|
321 | }
|
322 | fs.readdirSync('node_modules').filter(x => ['.bin'].indexOf(x) === -1).forEach(mod => {
|
323 | nodeModules[mod] = `commonjs2 ${mod}`;
|
324 | });
|
325 | if (isCache) {
|
326 | utils.writeFile(cacheFile, nodeModules);
|
327 | }
|
328 | return nodeModules;
|
329 | };
|
330 |
|
331 | utils.getIp = position => {
|
332 | const interfaces = os.networkInterfaces();
|
333 | const ips = [];
|
334 |
|
335 | if (interfaces.en0) {
|
336 | for (let i = 0; i < interfaces.en0.length; i++) {
|
337 | if (interfaces.en0[i].family === 'IPv4') {
|
338 | ips.push(interfaces.en0[i].address);
|
339 | }
|
340 | }
|
341 | }
|
342 | if (interfaces.en1) {
|
343 | for (let i = 0; i < interfaces.en1.length; i++) {
|
344 | if (interfaces.en1[i].family === 'IPv4') {
|
345 | ips.push(interfaces.en1[i].address);
|
346 | }
|
347 | }
|
348 | }
|
349 | if (position > 0 && position <= ips.length) {
|
350 | return ips[position - 1];
|
351 | } else if (ips.length) {
|
352 | return ips[0];
|
353 | }
|
354 | return '127.0.0.1';
|
355 |
|
356 | };
|
357 |
|
358 | utils.getHost = port => {
|
359 | const ip = utils.getIp();
|
360 | return `http://${ip}:${port || 9000}`;
|
361 | };
|
362 |
|
363 | utils.writeFile = (filepath, content) => {
|
364 | try {
|
365 | mkdirp.sync(path.dirname(filepath));
|
366 | fs.writeFileSync(filepath, typeof content === 'string' ? content : JSON.stringify(content), 'utf8');
|
367 | } catch (e) {
|
368 | console.error(`writeFile ${filepath} err`, e);
|
369 | }
|
370 | };
|
371 |
|
372 | utils.readFile = filepath => {
|
373 | try {
|
374 | if (fs.existsSync(filepath)) {
|
375 | const content = fs.readFileSync(filepath, 'utf8');
|
376 |
|
377 | return JSON.parse(content);
|
378 | }
|
379 | } catch (e) {
|
380 |
|
381 | }
|
382 | return undefined;
|
383 | };
|
384 |
|
385 |
|
386 | utils.saveBuildConfig = (filepath, buildConfig) => {
|
387 | utils.writeFile(filepath, buildConfig);
|
388 | };
|
389 |
|
390 | utils.getVersion = (name, baseDir) => {
|
391 | const pkgFile = path.join(baseDir, 'node_modules', name, 'package.json');
|
392 | if (fs.existsSync(pkgFile)) {
|
393 | const pkgJSON = require(pkgFile);
|
394 | return pkgJSON.version;
|
395 | }
|
396 | return null;
|
397 | };
|
398 |
|
399 | utils.getCompileTempDir = (filename = '', baseDir) => {
|
400 | const pkgfile = path.join(baseDir || process.cwd(), 'package.json');
|
401 | const pkg = require(pkgfile);
|
402 | const project = pkg.name;
|
403 | return path.join(os.tmpdir(), 'easywebpack', project, filename);
|
404 | };
|
405 |
|
406 | utils.getDllFilePath = (name, env = 'dev') => {
|
407 | const dir = utils.getDllManifestDir(env);
|
408 | return path.join(dir, `dll-${name}.json`);
|
409 | };
|
410 |
|
411 | utils.getDllCompileFileDir = (env = 'dev') => {
|
412 | const dir = utils.getDllManifestDir(env);
|
413 | return path.join(dir, 'file');
|
414 | };
|
415 | utils.getDllManifestDir = (env = 'dev') => {
|
416 | return utils.getCompileTempDir(`${env}/dll`);
|
417 | };
|
418 | utils.getDllManifestPath = (name, env = 'dev') => {
|
419 | const dir = utils.getDllManifestDir(env);
|
420 | return path.join(dir, `manifest-${name}.json`);
|
421 | };
|
422 |
|
423 | utils.getDllConfig = dll => {
|
424 | if (utils.isString(dll)) {
|
425 | return [{ name: 'vendor', lib: [dll] }];
|
426 | } else if (utils.isObject(dll) && dll.name && dll.lib) {
|
427 | return [dll];
|
428 | } else if (Array.isArray(dll) && dll.length && utils.isString(dll[0])) {
|
429 | return [{ name: 'vendor', lib: dll }];
|
430 | } else if (Array.isArray(dll) && dll.length && utils.isObject(dll[0]) && dll[0].name) {
|
431 | return dll;
|
432 | }
|
433 | return [];
|
434 | };
|
435 |
|
436 | utils.getDllCacheInfoPath = (name, env = 'dev') => {
|
437 | return utils.getCompileTempDir(`${env}/dll/cache-${name}.json`);
|
438 | };
|
439 |
|
440 | utils.getCacheLoaderInfoPath = (loader, env = 'dev', type = 'client') => {
|
441 | return utils.getCompileTempDir(`${env}/cache/${type}/${loader}`);
|
442 | };
|
443 |
|
444 | utils.getCacheInfoPath = () => {
|
445 | return utils.getCompileTempDir('config.json');
|
446 | };
|
447 |
|
448 | utils.setCacheInfoPath = json => {
|
449 | const cachePath = utils.getCacheInfoPath();
|
450 | utils.writeFile(cachePath, json);
|
451 | };
|
452 |
|
453 | utils.getModuleInfo = (module, baseDir) => {
|
454 | if (/\.js$/.test(module)) {
|
455 | if (fs.existsSync(module)) {
|
456 | const stat = fs.statSync(module);
|
457 | return stat.mtimeMs;
|
458 | }
|
459 | const moduleFile = path.join(baseDir, 'node_modules', module);
|
460 | const stat = fs.statSync(moduleFile);
|
461 | return stat.mtimeMs;
|
462 | }
|
463 | const pkgFile = path.join(baseDir, 'node_modules', module, 'package.json');
|
464 | if (fs.existsSync(pkgFile)) {
|
465 | const pkgJSON = require(pkgFile);
|
466 | return pkgJSON.version;
|
467 | }
|
468 | const moduleIndexFile = path.join(baseDir, 'node_modules', module, 'index.js');
|
469 | if (fs.existsSync(moduleIndexFile)) {
|
470 | const stat = fs.statSync(moduleIndexFile);
|
471 | return stat.mtimeMs;
|
472 | }
|
473 | return module;
|
474 | };
|
475 |
|
476 | utils.saveDllCacheInfo = (config, webpackConfigFile, dllCachePath, webpackDllInfo, webpackConfigFileLastModifyTime) => {
|
477 | const webpackDllLibInfo = {};
|
478 | webpackDllInfo.lib.forEach(module => {
|
479 | const info = utils.getModuleInfo(module, config.baseDir);
|
480 | webpackDllLibInfo[module] = info;
|
481 | });
|
482 | utils.writeFile(dllCachePath, {
|
483 | webpackDllInfo,
|
484 | webpackDllLibInfo,
|
485 | webpackConfigFile,
|
486 | webpackConfigFileLastModifyTime
|
487 | });
|
488 | };
|
489 |
|
490 | utils.getDllCacheInfo = dllCachePath => {
|
491 | if (fs.existsSync(dllCachePath)) {
|
492 | return require(dllCachePath);
|
493 | }
|
494 | return null;
|
495 | };
|
496 |
|
497 | utils.checkDllUpdate = (config, dll) => {
|
498 | const baseDir = config.baseDir;
|
499 | const filepath = config.webpackConfigFile ? config.webpackConfigFile : path.join(baseDir, 'webpack.config.js');
|
500 | const dllCachePath = utils.getDllCacheInfoPath(dll.name, config.env);
|
501 |
|
502 | if (!fs.existsSync(dllCachePath)) {
|
503 | utils.saveDllCacheInfo(config, filepath, dllCachePath, dll);
|
504 | return true;
|
505 | }
|
506 |
|
507 | if (!fs.existsSync(utils.getDllFilePath(dll.name, config.env))) {
|
508 | return true;
|
509 | }
|
510 | const dllCacheInfo = utils.getDllCacheInfo(dllCachePath);
|
511 |
|
512 | if (fs.existsSync(filepath)) {
|
513 | const stat = fs.statSync(filepath);
|
514 | const lastModifyTime = stat.mtimeMs;
|
515 |
|
516 | const webpackConfigFileLastModifyTime = dllCacheInfo.webpackConfigFileLastModifyTime
|
517 | if (webpackConfigFileLastModifyTime && webpackConfigFileLastModifyTime !== lastModifyTime) {
|
518 | utils.saveDllCacheInfo(config, filepath, dllCachePath, dll, lastModifyTime);
|
519 | return true;
|
520 | }
|
521 | }
|
522 |
|
523 | const webpackDllInfo = dllCacheInfo.webpackDllInfo;
|
524 | if (JSON.stringify(dll) !== JSON.stringify(webpackDllInfo)) {
|
525 | utils.saveDllCacheInfo(config, filepath, dllCachePath, dll, +new Date());
|
526 | return true;
|
527 | }
|
528 |
|
529 | const webpackDllLibInfo = dllCacheInfo.webpackDllLibInfo;
|
530 | return dll.lib.some(module => {
|
531 | const info = utils.getModuleInfo(module, config.baseDir);
|
532 | if (webpackDllLibInfo[module] !== info) {
|
533 | utils.saveDllCacheInfo(config, filepath, dllCachePath, dll, +new Date());
|
534 | return true;
|
535 | }
|
536 | return false;
|
537 | });
|
538 | };
|
539 |
|
540 | utils.isEgg = config => {
|
541 | if (config.egg) {
|
542 | return true;
|
543 | }
|
544 | const pkg = require(path.join(config.baseDir, 'package.json'));
|
545 | const vuePKGName = 'egg-view-vue-ssr';
|
546 | const reactPKGName = 'egg-view-react-ssr';
|
547 | const hasDeps = pkg.dependencies[vuePKGName] || pkg.dependencies[reactPKGName];
|
548 | if (hasDeps) {
|
549 | return true;
|
550 | }
|
551 | const vuePKGPath = path.join(config.baseDir, 'node_modules', vuePKGName);
|
552 | if (fs.existsSync(vuePKGPath)) {
|
553 | return true;
|
554 | }
|
555 | const reactPKGPath = path.join(config.baseDir, 'node_modules', reactPKGName);
|
556 | if (fs.existsSync(reactPKGPath)) {
|
557 | return true;
|
558 | }
|
559 | return false;
|
560 | };
|
561 |
|
562 | utils.getPluginLabel = plugin => {
|
563 | return md5(plugin.apply.toString());
|
564 | };
|
565 |
|
566 | utils.getConfigPlugin = (plugins = {}, label) => {
|
567 | if (Array.isArray(plugins)) {
|
568 | const plugin = plugins.find(item => {
|
569 | return item.hasOwnProperty(label);
|
570 | });
|
571 | return plugin && plugin[label];
|
572 | }
|
573 | return plugins[label];
|
574 | };
|
575 |
|
576 | module.exports = utils; |
\ | No newline at end of file |