UNPKG

17.1 kBJavaScriptView Raw
1const crypto = require('crypto');
2const fs = require('fs');
3const path = require('path');
4
5const lodash = require('lodash');
6const _mkdirp = require('mkdirp');
7const _rimraf = require('rimraf');
8const nodeObjectHash = require('node-object-hash');
9
10const envHash = require('./lib/envHash');
11const defaultConfigHash = require('./lib/defaultConfigHash');
12const promisify = require('./lib/util/promisify');
13const relateContext = require('./lib/util/relate-context');
14const pluginCompat = require('./lib/util/plugin-compat');
15const logMessages = require('./lib/util/log-messages');
16
17const LoggerFactory = require('./lib/loggerFactory');
18
19const cachePrefix = require('./lib/util').cachePrefix;
20
21const CacheSerializerFactory = require('./lib/CacheSerializerFactory');
22const SerializerAppendPlugin = require('./lib/SerializerAppendPlugin');
23const SerializerAppend2Plugin = require('./lib/SerializerAppend2Plugin');
24const SerializerCacachePlugin = require('./lib/SerializerCacachePlugin');
25const SerializerJsonPlugin = require('./lib/SerializerJsonPlugin');
26const HardSourceLevelDbSerializerPlugin = require('./lib/SerializerLeveldbPlugin');
27
28const hardSourceVersion = require('./package.json').version;
29
30function requestHash(request) {
31 return crypto
32 .createHash('sha1')
33 .update(request)
34 .digest()
35 .hexSlice();
36}
37
38const mkdirp = promisify(_mkdirp, { context: _mkdirp });
39mkdirp.sync = _mkdirp.sync.bind(_mkdirp);
40const rimraf = promisify(_rimraf);
41rimraf.sync = _rimraf.sync.bind(_rimraf);
42const fsReadFile = promisify(fs.readFile, { context: fs });
43const fsWriteFile = promisify(fs.writeFile, { context: fs });
44
45const 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
71const compilerContext = relateContext.compilerContext;
72const relateNormalPath = relateContext.relateNormalPath;
73const contextNormalPath = relateContext.contextNormalPath;
74const contextNormalPathSet = relateContext.contextNormalPathSet;
75
76function relateNormalRequest(compiler, key) {
77 return key
78 .split('!')
79 .map(subkey => relateNormalPath(compiler, subkey))
80 .join('!');
81}
82
83function relateNormalModuleId(compiler, id) {
84 return id.substring(0, 24) + relateNormalRequest(compiler, id.substring(24));
85}
86
87function contextNormalRequest(compiler, key) {
88 return key
89 .split('!')
90 .map(subkey => contextNormalPath(compiler, subkey))
91 .join('!');
92}
93
94function contextNormalModuleId(compiler, id) {
95 return id.substring(0, 24) + contextNormalRequest(compiler, id.substring(24));
96}
97
98function contextNormalLoaders(compiler, loaders) {
99 return loaders.map(loader =>
100 Object.assign({}, loader, {
101 loader: contextNormalPath(compiler, loader.loader),
102 }),
103 );
104}
105
106function contextNormalPathArray(compiler, paths) {
107 return paths.map(subpath => contextNormalPath(compiler, subpath));
108}
109
110class 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 // Reset the cache, we can't use it do to an environment change.
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 // console.log('cache in', Date.now() - start);
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 // console.log('cache out', Date.now() - startCacheTime);
538 });
539 });
540 }
541}
542
543module.exports = HardSourceWebpackPlugin;
544
545HardSourceWebpackPlugin.SerializerAppendPlugin = SerializerAppendPlugin;
546HardSourceWebpackPlugin.SerializerAppend2Plugin = SerializerAppend2Plugin;
547HardSourceWebpackPlugin.SerializerCacachePlugin = SerializerCacachePlugin;
548HardSourceWebpackPlugin.SerializerJsonPlugin = SerializerJsonPlugin;
549HardSourceWebpackPlugin.HardSourceLevelDbSerializerPlugin = HardSourceLevelDbSerializerPlugin;