UNPKG

18.2 kBJavaScriptView Raw
1'use strict';
2const path = require('path');
3const fs = require('fs');
4const os = require('os');
5const assert = require('assert');
6const url = require('url');
7const queryString = require('querystring');
8const mkdirp = require('mkdirp');
9const cloneDeep = require('lodash.clonedeep');
10const cloneDeepWith = require('lodash.clonedeepwith');
11const uniq = require('lodash.uniq');
12const get = require('lodash.get');
13const set = require('lodash.set');
14const has = require('lodash.has');
15const merge = require('lodash.merge');
16const install = require('./install');
17const glob = require('glob');
18const md5 = require('md5');
19const 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
32utils.has = value => {
33 return value !== undefined;
34};
35
36utils.isFunction = value => typeof value === 'function';
37
38utils.isObject = value => Object.prototype.toString.call(value) === '[object Object]';
39
40utils.isString = value => typeof value === 'string';
41
42utils.isBoolean = value => typeof value === 'boolean';
43
44utils.normalizePath = (filepath, baseDir) => path.isAbsolute(filepath) ? filepath : path.join(baseDir, filepath);
45
46utils.normalizeBuildPath = (buildPath, baseDir) => {
47 return utils.normalizePath(buildPath, baseDir);
48};
49
50utils.isHttpOrHttps = strUrl => {
51 return /^(https?:|\/\/)/.test(strUrl);
52};
53
54utils.normalizePublicPath = publicPath => {
55 publicPath = `${publicPath.replace(/\/$/, '')}/`;
56 if (!utils.isHttpOrHttps(publicPath) && !/^\//.test(publicPath)) {
57 publicPath = `/${publicPath}`;
58 }
59 return publicPath;
60};
61
62utils.isTrue = value => value !== 'false' && (!!value || value === undefined);
63
64utils.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
73utils.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
83utils.getOutputPath = config => {
84 const { output = {}, buildPath } = config;
85 if (output.path) {
86 return output.path;
87 }
88 return buildPath;
89};
90
91utils.getOutputPublicPath = config => {
92 const { output = {}, publicPath } = config;
93 if (output.publicPath) {
94 return output.publicPath.replace(/\/$/, '');
95 }
96 return publicPath.replace(/\/$/, '');
97};
98
99utils.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
107utils.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 => { // ['app/web/page', { 'app/app': 'app/web/page/app/app.js?loader=false' }],
133 if (utils.isString(entry)) { // isDirectory
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// support 'app/web/page/**!(component|components|view|views)/*.vue'
155utils.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
172utils.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
195utils.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
218utils.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
230utils.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 // console.log('----walkFile', entryDir, filePath);
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
264utils.isMatch = (regexArray, strMatch) => {
265 if (!regexArray) {
266 return false;
267 }
268 // fix window path seperate \\
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
274utils.assetsPath = (prefix, filepath) => path.posix.join(prefix, filepath);
275
276utils.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 // TODO:
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
299utils.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 // const loaders = utils.isFunction(loader.use) ? loader.use.apply(ctx) : loader.use;
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
323utils.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
338utils.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
360utils.getHost = port => {
361 const ip = utils.getIp();
362 return `http://${ip}:${port || 9000}`;
363};
364
365utils.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
374utils.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 /* istanbul ignore next */
383 }
384 return undefined;
385};
386
387
388utils.saveBuildConfig = (filepath, buildConfig) => {
389 utils.writeFile(filepath, buildConfig);
390};
391
392utils.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
401utils.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
408utils.getDllFilePath = (name, env = 'dev') => {
409 const dir = utils.getDllManifestDir(env);
410 return path.join(dir, `dll-${name}.json`);
411};
412
413utils.getDllCompileFileDir = (env = 'dev') => {
414 const dir = utils.getDllManifestDir(env);
415 return path.join(dir, 'file');
416};
417utils.getDllManifestDir = (env = 'dev') => {
418 return utils.getCompileTempDir(`${env}/dll`);
419};
420utils.getDllManifestPath = (name, env = 'dev') => {
421 const dir = utils.getDllManifestDir(env);
422 return path.join(dir, `manifest-${name}.json`);
423};
424
425utils.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
438utils.getDllCacheInfoPath = (name, env = 'dev') => {
439 return utils.getCompileTempDir(`${env}/dll/cache-${name}.json`);
440};
441
442utils.getCacheLoaderInfoPath = (loader, env = 'dev', type = 'client') => {
443 return utils.getCompileTempDir(`${env}/cache/${type}/${loader}`);
444};
445
446utils.getCacheInfoPath = () => {
447 return utils.getCompileTempDir('config.json');
448};
449
450utils.setCacheInfoPath = json => {
451 const cachePath = utils.getCacheInfoPath();
452 utils.writeFile(cachePath, json);
453};
454
455utils.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
478utils.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
492utils.getDllCacheInfo = dllCachePath => {
493 if (fs.existsSync(dllCachePath)) {
494 return require(dllCachePath);
495 }
496 return null;
497};
498
499utils.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 // cache file 文件不存在
504 if (!fs.existsSync(dllCachePath)) {
505 utils.saveDllCacheInfo(config, filepath, dllCachePath, dll);
506 return true;
507 }
508 // dll manifest 文件不存在
509 if (!fs.existsSync(utils.getDllFilePath(dll.name, config.env))) {
510 return true;
511 }
512 const dllCacheInfo = utils.getDllCacheInfo(dllCachePath);
513 // webpack.config.js 修改
514 if (fs.existsSync(filepath)) {
515 const stat = fs.statSync(filepath);
516 const lastModifyTime = stat.mtimeMs;
517 // 判断 webpack.config.js 修改时间
518 const webpackConfigFileLastModifyTime = dllCacheInfo.webpackConfigFileLastModifyTime
519 if (webpackConfigFileLastModifyTime && webpackConfigFileLastModifyTime !== lastModifyTime) {
520 utils.saveDllCacheInfo(config, filepath, dllCachePath, dll, lastModifyTime);
521 return true;
522 }
523 }
524 // dll 配置不等
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 // 判断 module 版本是否有升级, 目前只判断主module, module 依赖变更的暂不支持
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
542utils.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
565utils.getPluginLabel = plugin => {
566 return md5(plugin.apply.toString());
567};
568
569utils.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
579module.exports = utils;
\No newline at end of file