UNPKG

17.8 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 shell = require('shelljs');
20const 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
33utils.has = value => {
34 return value !== undefined;
35};
36
37utils.isFunction = value => typeof value === 'function';
38
39utils.isObject = value => Object.prototype.toString.call(value) === '[object Object]';
40
41utils.isString = value => typeof value === 'string';
42
43utils.isBoolean = value => typeof value === 'boolean';
44
45utils.normalizePath = (filepath, baseDir) => path.isAbsolute(filepath) ? filepath : path.join(baseDir, filepath);
46
47utils.normalizeBuildPath = (buildPath, baseDir) => {
48 return utils.normalizePath(buildPath, baseDir);
49};
50
51utils.isHttpOrHttps = strUrl => {
52 return /^(https?:|\/\/)/.test(strUrl);
53};
54
55utils.normalizePublicPath = publicPath => {
56 publicPath = `${publicPath.replace(/\/$/, '')}/`;
57 if (!utils.isHttpOrHttps(publicPath) && !/^\//.test(publicPath)) {
58 publicPath = `/${publicPath}`;
59 }
60 return publicPath;
61};
62
63utils.isTrue = value => value !== 'false' && (!!value || value === undefined);
64
65utils.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
74utils.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
84utils.getOutputPath = config => {
85 const { output = {}, buildPath } = config;
86 if (output.path) {
87 return output.path;
88 }
89 return buildPath;
90};
91
92utils.getOutputPublicPath = config => {
93 const { output = {}, publicPath } = config;
94 if (output.publicPath) {
95 return output.publicPath.replace(/\/$/, '');
96 }
97 return publicPath.replace(/\/$/, '');
98};
99
100utils.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
108utils.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 => { // ['app/web/page', { 'app/app': 'app/web/page/app/app.js?loader=false' }],
126 if (utils.isString(entry)) { // isDirectory
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// support 'app/web/page/**!(component|components|view|views)/*.vue'
148utils.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
165utils.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
188utils.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
211utils.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
223utils.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 // console.log('----walkFile', entryDir, filePath);
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
257utils.isMatch = (regexArray, strMatch) => {
258 if (!regexArray) {
259 return false;
260 }
261 // fix window path seperate \\
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
267utils.assetsPath = (prefix, filepath) => path.posix.join(prefix, filepath);
268
269utils.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 // TODO:
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
292utils.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 // const loaders = utils.isFunction(loader.use) ? loader.use.apply(ctx) : loader.use;
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
316utils.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
331utils.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
358utils.getHost = port => {
359 const ip = utils.getIp();
360 return `http://${ip}:${port || 9000}`;
361};
362
363utils.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
372utils.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 /* istanbul ignore next */
381 }
382 return undefined;
383};
384
385
386utils.saveBuildConfig = (filepath, buildConfig) => {
387 utils.writeFile(filepath, buildConfig);
388};
389
390utils.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
399utils.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
406utils.getDllFilePath = (name, env = 'dev') => {
407 const dir = utils.getDllManifestDir(env);
408 return path.join(dir, `dll-${name}.json`);
409};
410
411utils.getDllCompileFileDir = (env = 'dev') => {
412 const dir = utils.getDllManifestDir(env);
413 return path.join(dir, 'file');
414};
415utils.getDllManifestDir = (env = 'dev') => {
416 return utils.getCompileTempDir(`${env}/dll`);
417};
418utils.getDllManifestPath = (name, env = 'dev') => {
419 const dir = utils.getDllManifestDir(env);
420 return path.join(dir, `manifest-${name}.json`);
421};
422
423utils.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
436utils.getDllCacheInfoPath = (name, env = 'dev') => {
437 return utils.getCompileTempDir(`${env}/dll/cache-${name}.json`);
438};
439
440utils.getCacheLoaderInfoPath = (loader, env = 'dev', type = 'client') => {
441 return utils.getCompileTempDir(`${env}/cache/${type}/${loader}`);
442};
443
444utils.getCacheInfoPath = () => {
445 return utils.getCompileTempDir('config.json');
446};
447
448utils.setCacheInfoPath = json => {
449 const cachePath = utils.getCacheInfoPath();
450 utils.writeFile(cachePath, json);
451};
452
453utils.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
476utils.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
490utils.getDllCacheInfo = dllCachePath => {
491 if (fs.existsSync(dllCachePath)) {
492 return require(dllCachePath);
493 }
494 return null;
495};
496
497utils.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 // cache file 文件不存在
502 if (!fs.existsSync(dllCachePath)) {
503 utils.saveDllCacheInfo(config, filepath, dllCachePath, dll);
504 return true;
505 }
506 // dll manifest 文件不存在
507 if (!fs.existsSync(utils.getDllFilePath(dll.name, config.env))) {
508 return true;
509 }
510 const dllCacheInfo = utils.getDllCacheInfo(dllCachePath);
511 // webpack.config.js 修改
512 if (fs.existsSync(filepath)) {
513 const stat = fs.statSync(filepath);
514 const lastModifyTime = stat.mtimeMs;
515 // 判断 webpack.config.js 修改时间
516 const webpackConfigFileLastModifyTime = dllCacheInfo.webpackConfigFileLastModifyTime
517 if (webpackConfigFileLastModifyTime && webpackConfigFileLastModifyTime !== lastModifyTime) {
518 utils.saveDllCacheInfo(config, filepath, dllCachePath, dll, lastModifyTime);
519 return true;
520 }
521 }
522 // dll 配置不等
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 // 判断 module 版本是否有升级, 目前只判断主module, module 依赖变更的暂不支持
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
540utils.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
562utils.getPluginLabel = plugin => {
563 return md5(plugin.apply.toString());
564};
565
566module.exports = utils;
\No newline at end of file