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