1 | const crypto = require('crypto');
|
2 | const fs = require('fs');
|
3 | const path = require('path');
|
4 |
|
5 | const lodash = require('lodash');
|
6 | const _mkdirp = require('mkdirp');
|
7 | const _rimraf = require('rimraf');
|
8 | const nodeObjectHash = require('node-object-hash');
|
9 |
|
10 | const envHash = require('./lib/envHash');
|
11 | const defaultConfigHash = require('./lib/defaultConfigHash');
|
12 | const promisify = require('./lib/util/promisify');
|
13 | const relateContext = require('./lib/util/relate-context');
|
14 | const pluginCompat = require('./lib/util/plugin-compat');
|
15 | const logMessages = require('./lib/util/log-messages');
|
16 |
|
17 | const LoggerFactory = require('./lib/loggerFactory');
|
18 |
|
19 | const cachePrefix = require('./lib/util').cachePrefix;
|
20 |
|
21 | const CacheSerializerFactory = require('./lib/CacheSerializerFactory');
|
22 | const SerializerAppendPlugin = require('./lib/SerializerAppendPlugin');
|
23 | const SerializerAppend2Plugin = require('./lib/SerializerAppend2Plugin');
|
24 | const SerializerCacachePlugin = require('./lib/SerializerCacachePlugin');
|
25 | const SerializerJsonPlugin = require('./lib/SerializerJsonPlugin');
|
26 | const HardSourceLevelDbSerializerPlugin = require('./lib/SerializerLeveldbPlugin');
|
27 |
|
28 | const hardSourceVersion = require('./package.json').version;
|
29 |
|
30 | function requestHash(request) {
|
31 | return crypto
|
32 | .createHash('sha1')
|
33 | .update(request)
|
34 | .digest()
|
35 | .hexSlice();
|
36 | }
|
37 |
|
38 | const mkdirp = promisify(_mkdirp, { context: _mkdirp });
|
39 | mkdirp.sync = _mkdirp.sync.bind(_mkdirp);
|
40 | const rimraf = promisify(_rimraf);
|
41 | rimraf.sync = _rimraf.sync.bind(_rimraf);
|
42 | const fsReadFile = promisify(fs.readFile, { context: fs });
|
43 | const fsWriteFile = promisify(fs.writeFile, { context: fs });
|
44 |
|
45 | const bulkFsTask = (array, each) =>
|
46 | new Promise((resolve, reject) => {
|
47 | let ops = 0;
|
48 | const out = [];
|
49 | array.forEach((item, i) => {
|
50 | out[i] = each(item, (back, callback) => {
|
51 | ops++;
|
52 | return (err, value) => {
|
53 | try {
|
54 | out[i] = back(err, value, out[i]);
|
55 | } catch (e) {
|
56 | return reject(e);
|
57 | }
|
58 |
|
59 | ops--;
|
60 | if (ops === 0) {
|
61 | resolve(out);
|
62 | }
|
63 | };
|
64 | });
|
65 | });
|
66 | if (ops === 0) {
|
67 | resolve(out);
|
68 | }
|
69 | });
|
70 |
|
71 | const compilerContext = relateContext.compilerContext;
|
72 | const relateNormalPath = relateContext.relateNormalPath;
|
73 | const contextNormalPath = relateContext.contextNormalPath;
|
74 | const contextNormalPathSet = relateContext.contextNormalPathSet;
|
75 |
|
76 | function relateNormalRequest(compiler, key) {
|
77 | return key
|
78 | .split('!')
|
79 | .map(subkey => relateNormalPath(compiler, subkey))
|
80 | .join('!');
|
81 | }
|
82 |
|
83 | function relateNormalModuleId(compiler, id) {
|
84 | return id.substring(0, 24) + relateNormalRequest(compiler, id.substring(24));
|
85 | }
|
86 |
|
87 | function contextNormalRequest(compiler, key) {
|
88 | return key
|
89 | .split('!')
|
90 | .map(subkey => contextNormalPath(compiler, subkey))
|
91 | .join('!');
|
92 | }
|
93 |
|
94 | function contextNormalModuleId(compiler, id) {
|
95 | return id.substring(0, 24) + contextNormalRequest(compiler, id.substring(24));
|
96 | }
|
97 |
|
98 | function contextNormalLoaders(compiler, loaders) {
|
99 | return loaders.map(loader =>
|
100 | Object.assign({}, loader, {
|
101 | loader: contextNormalPath(compiler, loader.loader),
|
102 | }),
|
103 | );
|
104 | }
|
105 |
|
106 | function contextNormalPathArray(compiler, paths) {
|
107 | return paths.map(subpath => contextNormalPath(compiler, subpath));
|
108 | }
|
109 |
|
110 | class HardSourceWebpackPlugin {
|
111 | constructor(options) {
|
112 | this.options = options || {};
|
113 | }
|
114 |
|
115 | getPath(dirName, suffix) {
|
116 | const confighashIndex = dirName.search(/\[confighash\]/);
|
117 | if (confighashIndex !== -1) {
|
118 | dirName = dirName.replace(/\[confighash\]/, this.configHash);
|
119 | }
|
120 | let cachePath = path.resolve(
|
121 | process.cwd(),
|
122 | this.compilerOutputOptions.path,
|
123 | dirName,
|
124 | );
|
125 | if (suffix) {
|
126 | cachePath = path.join(cachePath, suffix);
|
127 | }
|
128 | return cachePath;
|
129 | }
|
130 |
|
131 | getCachePath(suffix) {
|
132 | return this.getPath(this.options.cacheDirectory, suffix);
|
133 | }
|
134 |
|
135 | apply(compiler) {
|
136 | const options = this.options;
|
137 | let active = true;
|
138 |
|
139 | const logger = new LoggerFactory(compiler).create();
|
140 |
|
141 | const loggerCore = logger.from('core');
|
142 | logger.lock();
|
143 |
|
144 | const compilerHooks = pluginCompat.hooks(compiler);
|
145 |
|
146 | if (!compiler.options.cache) {
|
147 | compiler.options.cache = true;
|
148 | }
|
149 |
|
150 | if (!options.cacheDirectory) {
|
151 | options.cacheDirectory = path.resolve(
|
152 | process.cwd(),
|
153 | compiler.options.context,
|
154 | 'node_modules/.cache/hard-source/[confighash]',
|
155 | );
|
156 | }
|
157 |
|
158 | this.compilerOutputOptions = compiler.options.output;
|
159 | if (!options.configHash) {
|
160 | options.configHash = defaultConfigHash;
|
161 | }
|
162 | if (options.configHash) {
|
163 | if (typeof options.configHash === 'string') {
|
164 | this.configHash = options.configHash;
|
165 | } else if (typeof options.configHash === 'function') {
|
166 | this.configHash = options.configHash(compiler.options);
|
167 | }
|
168 | compiler.__hardSource_configHash = this.configHash;
|
169 | compiler.__hardSource_shortConfigHash = this.configHash.substring(0, 8);
|
170 | }
|
171 | const configHashInDirectory =
|
172 | options.cacheDirectory.search(/\[confighash\]/) !== -1;
|
173 | if (configHashInDirectory && !this.configHash) {
|
174 | logMessages.configHashSetButNotUsed(compiler, {
|
175 | cacheDirectory: options.cacheDirectory,
|
176 | });
|
177 | active = false;
|
178 |
|
179 | function unlockLogger() {
|
180 | logger.unlock();
|
181 | }
|
182 | compilerHooks.watchRun.tap('HardSource - index', unlockLogger);
|
183 | compilerHooks.run.tap('HardSource - index', unlockLogger);
|
184 | return;
|
185 | }
|
186 |
|
187 | let environmentHasher = null;
|
188 | if (typeof options.environmentHash !== 'undefined') {
|
189 | if (options.environmentHash === false) {
|
190 | environmentHasher = () => Promise.resolve('');
|
191 | } else if (typeof options.environmentHash === 'string') {
|
192 | environmentHasher = () => Promise.resolve(options.environmentHash);
|
193 | } else if (typeof options.environmentHash === 'object') {
|
194 | environmentHasher = () => envHash(options.environmentHash);
|
195 | environmentHasher.inputs = () => envHash.inputs(options.environmentHash);
|
196 | } else if (typeof options.environmentHash === 'function') {
|
197 | environmentHasher = () => Promise.resolve(options.environmentHash());
|
198 | if (options.environmentHash.inputs) {
|
199 | environmentHasher.inputs = () => Promise.resolve(options.environmentHasher.inputs());
|
200 | };
|
201 | }
|
202 | }
|
203 | if (!environmentHasher) {
|
204 | environmentHasher = envHash;
|
205 | }
|
206 |
|
207 | const cacheDirPath = this.getCachePath();
|
208 | const cacheAssetDirPath = path.join(cacheDirPath, 'assets');
|
209 | const resolveCachePath = path.join(cacheDirPath, 'resolve.json');
|
210 |
|
211 | let currentStamp = '';
|
212 |
|
213 | const cacheSerializerFactory = new CacheSerializerFactory(compiler);
|
214 | let createSerializers = true;
|
215 | let cacheRead = false;
|
216 |
|
217 | const _this = this;
|
218 |
|
219 | pluginCompat.register(compiler, '_hardSourceCreateSerializer', 'sync', [
|
220 | 'cacheSerializerFactory',
|
221 | 'cacheDirPath',
|
222 | ]);
|
223 | pluginCompat.register(compiler, '_hardSourceResetCache', 'sync', []);
|
224 | pluginCompat.register(compiler, '_hardSourceReadCache', 'asyncParallel', [
|
225 | 'relativeHelpers',
|
226 | ]);
|
227 | pluginCompat.register(
|
228 | compiler,
|
229 | '_hardSourceVerifyCache',
|
230 | 'asyncParallel',
|
231 | [],
|
232 | );
|
233 | pluginCompat.register(compiler, '_hardSourceWriteCache', 'asyncParallel', [
|
234 | 'compilation',
|
235 | 'relativeHelpers',
|
236 | ]);
|
237 |
|
238 | function runReadOrReset(_compiler) {
|
239 | logger.unlock();
|
240 |
|
241 | if (!active) {
|
242 | return Promise.resolve();
|
243 | }
|
244 |
|
245 | try {
|
246 | fs.statSync(cacheAssetDirPath);
|
247 | } catch (_) {
|
248 | mkdirp.sync(cacheAssetDirPath);
|
249 | logMessages.configHashFirstBuild(compiler, {
|
250 | cacheDirPath,
|
251 | configHash: compiler.__hardSource_configHash,
|
252 | });
|
253 | }
|
254 | const start = Date.now();
|
255 |
|
256 | if (createSerializers) {
|
257 | createSerializers = false;
|
258 | try {
|
259 | compilerHooks._hardSourceCreateSerializer.call(
|
260 | cacheSerializerFactory,
|
261 | cacheDirPath,
|
262 | );
|
263 | } catch (err) {
|
264 | return Promise.reject(err);
|
265 | }
|
266 | }
|
267 |
|
268 | return Promise.all([
|
269 | fsReadFile(path.join(cacheDirPath, 'stamp'), 'utf8').catch(() => ''),
|
270 |
|
271 | environmentHasher(),
|
272 |
|
273 | fsReadFile(path.join(cacheDirPath, 'version'), 'utf8').catch(() => ''),
|
274 |
|
275 | environmentHasher.inputs ? environmentHasher.inputs() : null,
|
276 | ]).then(([stamp, hash, versionStamp, hashInputs]) => {
|
277 | if (!configHashInDirectory && options.configHash) {
|
278 | hash += `_${_this.configHash}`;
|
279 | }
|
280 |
|
281 | if (hashInputs && !cacheRead) {
|
282 | logMessages.environmentInputs(compiler, {inputs: hashInputs});
|
283 | }
|
284 |
|
285 | currentStamp = hash;
|
286 | if (!hash || hash !== stamp || hardSourceVersion !== versionStamp) {
|
287 | if (hash && stamp) {
|
288 | if (configHashInDirectory) {
|
289 | logMessages.environmentHashChanged(compiler);
|
290 | }
|
291 | else {
|
292 | logMessages.configHashChanged(compiler);
|
293 | }
|
294 | } else if (versionStamp && hardSourceVersion !== versionStamp) {
|
295 | logMessages.hardSourceVersionChanged(compiler);
|
296 | }
|
297 |
|
298 |
|
299 | pluginCompat.call(compiler, '_hardSourceResetCache', []);
|
300 |
|
301 | return rimraf(cacheDirPath);
|
302 | }
|
303 |
|
304 | if (cacheRead) {
|
305 | return Promise.resolve();
|
306 | }
|
307 | cacheRead = true;
|
308 |
|
309 | logMessages.configHashBuildWith(compiler, {
|
310 | cacheDirPath,
|
311 | configHash: compiler.__hardSource_configHash,
|
312 | });
|
313 |
|
314 | function contextKeys(compiler, fn) {
|
315 | return source => {
|
316 | const dest = {};
|
317 | Object.keys(source).forEach(key => {
|
318 | dest[fn(compiler, key)] = source[key];
|
319 | });
|
320 | return dest;
|
321 | };
|
322 | }
|
323 |
|
324 | function contextValues(compiler, fn) {
|
325 | return source => {
|
326 | const dest = {};
|
327 | Object.keys(source).forEach(key => {
|
328 | const value = fn(compiler, source[key], key);
|
329 | if (value) {
|
330 | dest[key] = value;
|
331 | }
|
332 | else {
|
333 | delete dest[key];
|
334 | }
|
335 | });
|
336 | return dest;
|
337 | };
|
338 | }
|
339 |
|
340 | function copyWithDeser(dest, source) {
|
341 | Object.keys(source).forEach(key => {
|
342 | const item = source[key];
|
343 | dest[key] = typeof item === 'string' ? JSON.parse(item) : item;
|
344 | });
|
345 | }
|
346 |
|
347 | return Promise.all([
|
348 | compilerHooks._hardSourceReadCache.promise({
|
349 | contextKeys,
|
350 | contextValues,
|
351 | contextNormalPath,
|
352 | contextNormalRequest,
|
353 | contextNormalModuleId,
|
354 | copyWithDeser,
|
355 | }),
|
356 | ]).then(() => {
|
357 |
|
358 | });
|
359 | });
|
360 | }
|
361 |
|
362 | function runVerify(_compiler) {
|
363 | if (!active) {
|
364 | return Promise.resolve();
|
365 | }
|
366 |
|
367 | const stats = {};
|
368 | return pluginCompat.promise(compiler, '_hardSourceVerifyCache', []);
|
369 | }
|
370 |
|
371 | compilerHooks.watchRun.tapPromise('HardSource - index - readOrReset', runReadOrReset);
|
372 | compilerHooks.run.tapPromise('HardSource - index - readOrReset', runReadOrReset);
|
373 |
|
374 | const detectModule = path => {
|
375 | try {
|
376 | require(path);
|
377 | return true;
|
378 | } catch (_) {
|
379 | return false;
|
380 | }
|
381 | };
|
382 |
|
383 | const webpackFeatures = {
|
384 | concatenatedModule: detectModule(
|
385 | 'webpack/lib/optimize/ConcatenatedModule',
|
386 | ),
|
387 | generator: detectModule('webpack/lib/JavascriptGenerator'),
|
388 | };
|
389 |
|
390 | let schemasVersion = 2;
|
391 | if (webpackFeatures.concatenatedModule) {
|
392 | schemasVersion = 3;
|
393 | }
|
394 | if (webpackFeatures.generator) {
|
395 | schemasVersion = 4;
|
396 | }
|
397 |
|
398 | const ArchetypeSystem = require('./lib/SystemArchetype');
|
399 | const ParitySystem = require('./lib/SystemParity');
|
400 |
|
401 | const AssetCache = require('./lib/CacheAsset');
|
402 | const ModuleCache = require('./lib/CacheModule');
|
403 |
|
404 | const EnhancedResolveCache = require('./lib/CacheEnhancedResolve');
|
405 | const Md5Cache = require('./lib/CacheMd5');
|
406 | const ModuleResolverCache = require('./lib/CacheModuleResolver');
|
407 |
|
408 | const TransformCompilationPlugin = require('./lib/TransformCompilationPlugin');
|
409 | const TransformAssetPlugin = require('./lib/TransformAssetPlugin');
|
410 | let TransformConcatenationModulePlugin;
|
411 | if (webpackFeatures.concatenatedModule) {
|
412 | TransformConcatenationModulePlugin = require('./lib/TransformConcatenationModulePlugin');
|
413 | }
|
414 | const TransformNormalModulePlugin = require('./lib/TransformNormalModulePlugin');
|
415 | const TransformNormalModuleFactoryPlugin = require('./lib/TransformNormalModuleFactoryPlugin');
|
416 | const TransformModuleAssetsPlugin = require('./lib/TransformModuleAssetsPlugin');
|
417 | const TransformModuleErrorsPlugin = require('./lib/TransformModuleErrorsPlugin');
|
418 | const SupportExtractTextPlugin = require('./lib/SupportExtractTextPlugin');
|
419 | let SupportMiniCssExtractPlugin;
|
420 | if (webpackFeatures.generator) {
|
421 | SupportMiniCssExtractPlugin = require('./lib/SupportMiniCssExtractPlugin');
|
422 | }
|
423 | const TransformDependencyBlockPlugin = require('./lib/TransformDependencyBlockPlugin');
|
424 | const TransformBasicDependencyPlugin = require('./lib/TransformBasicDependencyPlugin');
|
425 | let HardHarmonyDependencyPlugin;
|
426 | const TransformSourcePlugin = require('./lib/TransformSourcePlugin');
|
427 | const TransformParserPlugin = require('./lib/TransformParserPlugin');
|
428 | let TransformGeneratorPlugin;
|
429 | if (webpackFeatures.generator) {
|
430 | TransformGeneratorPlugin = require('./lib/TransformGeneratorPlugin');
|
431 | }
|
432 |
|
433 | const ChalkLoggerPlugin = require('./lib/ChalkLoggerPlugin');
|
434 |
|
435 | new ArchetypeSystem().apply(compiler);
|
436 | new ParitySystem().apply(compiler);
|
437 |
|
438 | new AssetCache().apply(compiler);
|
439 | new ModuleCache().apply(compiler);
|
440 |
|
441 | new EnhancedResolveCache().apply(compiler);
|
442 | new Md5Cache().apply(compiler);
|
443 | new ModuleResolverCache().apply(compiler);
|
444 |
|
445 | new TransformCompilationPlugin().apply(compiler);
|
446 |
|
447 | new TransformAssetPlugin().apply(compiler);
|
448 |
|
449 | new TransformNormalModulePlugin({
|
450 | schema: schemasVersion,
|
451 | }).apply(compiler);
|
452 | new TransformNormalModuleFactoryPlugin().apply(compiler);
|
453 |
|
454 | if (TransformConcatenationModulePlugin) {
|
455 | new TransformConcatenationModulePlugin().apply(compiler);
|
456 | }
|
457 |
|
458 | new TransformModuleAssetsPlugin().apply(compiler);
|
459 | new TransformModuleErrorsPlugin().apply(compiler);
|
460 | new SupportExtractTextPlugin().apply(compiler);
|
461 |
|
462 | if (SupportMiniCssExtractPlugin) {
|
463 | new SupportMiniCssExtractPlugin().apply(compiler);
|
464 | }
|
465 |
|
466 | new TransformDependencyBlockPlugin({
|
467 | schema: schemasVersion,
|
468 | }).apply(compiler);
|
469 |
|
470 | new TransformBasicDependencyPlugin({
|
471 | schema: schemasVersion,
|
472 | }).apply(compiler);
|
473 |
|
474 | new TransformSourcePlugin({
|
475 | schema: schemasVersion,
|
476 | }).apply(compiler);
|
477 |
|
478 | new TransformParserPlugin({
|
479 | schema: schemasVersion,
|
480 | }).apply(compiler);
|
481 |
|
482 | if (TransformGeneratorPlugin) {
|
483 | new TransformGeneratorPlugin({
|
484 | schema: schemasVersion,
|
485 | }).apply(compiler);
|
486 | }
|
487 |
|
488 | new ChalkLoggerPlugin(this.options.info).apply(compiler);
|
489 |
|
490 | compilerHooks.watchRun.tapPromise('HardSource - index - verify', runVerify);
|
491 | compilerHooks.run.tapPromise('HardSource - index - verify', runVerify);
|
492 |
|
493 | let freeze;
|
494 |
|
495 | compilerHooks._hardSourceMethods.tap('HardSource - index', methods => {
|
496 | freeze = methods.freeze;
|
497 | });
|
498 |
|
499 | compilerHooks.afterCompile.tapPromise('HardSource - index', compilation => {
|
500 | if (!active) {
|
501 | return Promise.resolve();
|
502 | }
|
503 |
|
504 | const startCacheTime = Date.now();
|
505 |
|
506 | const identifierPrefix = cachePrefix(compilation);
|
507 | if (identifierPrefix !== null) {
|
508 | freeze('Compilation', null, compilation, {
|
509 | compilation,
|
510 | });
|
511 | }
|
512 |
|
513 | return Promise.all([
|
514 | mkdirp(cacheDirPath).then(() =>
|
515 | Promise.all([
|
516 | fsWriteFile(path.join(cacheDirPath, 'stamp'), currentStamp, 'utf8'),
|
517 | fsWriteFile(
|
518 | path.join(cacheDirPath, 'version'),
|
519 | hardSourceVersion,
|
520 | 'utf8',
|
521 | ),
|
522 | ]),
|
523 | ),
|
524 | pluginCompat.promise(compiler, '_hardSourceWriteCache', [
|
525 | compilation,
|
526 | {
|
527 | relateNormalPath,
|
528 | relateNormalRequest,
|
529 | relateNormalModuleId,
|
530 |
|
531 | contextNormalPath,
|
532 | contextNormalRequest,
|
533 | contextNormalModuleId,
|
534 | },
|
535 | ]),
|
536 | ]).then(() => {
|
537 |
|
538 | });
|
539 | });
|
540 | }
|
541 | }
|
542 |
|
543 | module.exports = HardSourceWebpackPlugin;
|
544 |
|
545 | HardSourceWebpackPlugin.SerializerAppendPlugin = SerializerAppendPlugin;
|
546 | HardSourceWebpackPlugin.SerializerAppend2Plugin = SerializerAppend2Plugin;
|
547 | HardSourceWebpackPlugin.SerializerCacachePlugin = SerializerCacachePlugin;
|
548 | HardSourceWebpackPlugin.SerializerJsonPlugin = SerializerJsonPlugin;
|
549 | HardSourceWebpackPlugin.HardSourceLevelDbSerializerPlugin = HardSourceLevelDbSerializerPlugin;
|