UNPKG

34.5 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const parseJson = require("json-parse-better-errors");
9const asyncLib = require("neo-async");
10const {
11 SyncHook,
12 SyncBailHook,
13 AsyncParallelHook,
14 AsyncSeriesHook
15} = require("tapable");
16const { SizeOnlySource } = require("webpack-sources");
17const webpack = require("./");
18const Cache = require("./Cache");
19const CacheFacade = require("./CacheFacade");
20const ChunkGraph = require("./ChunkGraph");
21const Compilation = require("./Compilation");
22const ConcurrentCompilationError = require("./ConcurrentCompilationError");
23const ContextModuleFactory = require("./ContextModuleFactory");
24const ModuleGraph = require("./ModuleGraph");
25const NormalModuleFactory = require("./NormalModuleFactory");
26const RequestShortener = require("./RequestShortener");
27const ResolverFactory = require("./ResolverFactory");
28const Stats = require("./Stats");
29const Watching = require("./Watching");
30const WebpackError = require("./WebpackError");
31const { Logger } = require("./logging/Logger");
32const { join, dirname, mkdirp } = require("./util/fs");
33const { makePathsRelative } = require("./util/identifier");
34const { isSourceEqual } = require("./util/source");
35
36/** @typedef {import("webpack-sources").Source} Source */
37/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */
38/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */
39/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
40/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
41/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */
42/** @typedef {import("./Chunk")} Chunk */
43/** @typedef {import("./Dependency")} Dependency */
44/** @typedef {import("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */
45/** @typedef {import("./Module")} Module */
46/** @typedef {import("./util/WeakTupleMap")} WeakTupleMap */
47/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
48/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
49/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
50/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
51
52/**
53 * @typedef {Object} CompilationParams
54 * @property {NormalModuleFactory} normalModuleFactory
55 * @property {ContextModuleFactory} contextModuleFactory
56 */
57
58/**
59 * @template T
60 * @callback Callback
61 * @param {Error=} err
62 * @param {T=} result
63 */
64
65/**
66 * @callback RunAsChildCallback
67 * @param {Error=} err
68 * @param {Chunk[]=} entries
69 * @param {Compilation=} compilation
70 */
71
72/**
73 * @typedef {Object} AssetEmittedInfo
74 * @property {Buffer} content
75 * @property {Source} source
76 * @property {Compilation} compilation
77 * @property {string} outputPath
78 * @property {string} targetPath
79 */
80
81/**
82 * @param {string[]} array an array
83 * @returns {boolean} true, if the array is sorted
84 */
85const isSorted = array => {
86 for (let i = 1; i < array.length; i++) {
87 if (array[i - 1] > array[i]) return false;
88 }
89 return true;
90};
91
92/**
93 * @param {Object} obj an object
94 * @param {string[]} keys the keys of the object
95 * @returns {Object} the object with properties sorted by property name
96 */
97const sortObject = (obj, keys) => {
98 const o = {};
99 for (const k of keys.sort()) {
100 o[k] = obj[k];
101 }
102 return o;
103};
104
105/**
106 * @param {string} filename filename
107 * @param {string | string[] | undefined} hashes list of hashes
108 * @returns {boolean} true, if the filename contains any hash
109 */
110const includesHash = (filename, hashes) => {
111 if (!hashes) return false;
112 if (Array.isArray(hashes)) {
113 return hashes.some(hash => filename.includes(hash));
114 } else {
115 return filename.includes(hashes);
116 }
117};
118
119class Compiler {
120 /**
121 * @param {string} context the compilation path
122 */
123 constructor(context) {
124 this.hooks = Object.freeze({
125 /** @type {SyncHook<[]>} */
126 initialize: new SyncHook([]),
127
128 /** @type {SyncBailHook<[Compilation], boolean>} */
129 shouldEmit: new SyncBailHook(["compilation"]),
130 /** @type {AsyncSeriesHook<[Stats]>} */
131 done: new AsyncSeriesHook(["stats"]),
132 /** @type {SyncHook<[Stats]>} */
133 afterDone: new SyncHook(["stats"]),
134 /** @type {AsyncSeriesHook<[]>} */
135 additionalPass: new AsyncSeriesHook([]),
136 /** @type {AsyncSeriesHook<[Compiler]>} */
137 beforeRun: new AsyncSeriesHook(["compiler"]),
138 /** @type {AsyncSeriesHook<[Compiler]>} */
139 run: new AsyncSeriesHook(["compiler"]),
140 /** @type {AsyncSeriesHook<[Compilation]>} */
141 emit: new AsyncSeriesHook(["compilation"]),
142 /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
143 assetEmitted: new AsyncSeriesHook(["file", "info"]),
144 /** @type {AsyncSeriesHook<[Compilation]>} */
145 afterEmit: new AsyncSeriesHook(["compilation"]),
146
147 /** @type {SyncHook<[Compilation, CompilationParams]>} */
148 thisCompilation: new SyncHook(["compilation", "params"]),
149 /** @type {SyncHook<[Compilation, CompilationParams]>} */
150 compilation: new SyncHook(["compilation", "params"]),
151 /** @type {SyncHook<[NormalModuleFactory]>} */
152 normalModuleFactory: new SyncHook(["normalModuleFactory"]),
153 /** @type {SyncHook<[ContextModuleFactory]>} */
154 contextModuleFactory: new SyncHook(["contextModuleFactory"]),
155
156 /** @type {AsyncSeriesHook<[CompilationParams]>} */
157 beforeCompile: new AsyncSeriesHook(["params"]),
158 /** @type {SyncHook<[CompilationParams]>} */
159 compile: new SyncHook(["params"]),
160 /** @type {AsyncParallelHook<[Compilation]>} */
161 make: new AsyncParallelHook(["compilation"]),
162 /** @type {AsyncParallelHook<[Compilation]>} */
163 finishMake: new AsyncSeriesHook(["compilation"]),
164 /** @type {AsyncSeriesHook<[Compilation]>} */
165 afterCompile: new AsyncSeriesHook(["compilation"]),
166
167 /** @type {AsyncSeriesHook<[Compiler]>} */
168 watchRun: new AsyncSeriesHook(["compiler"]),
169 /** @type {SyncHook<[Error]>} */
170 failed: new SyncHook(["error"]),
171 /** @type {SyncHook<[string | null, number]>} */
172 invalid: new SyncHook(["filename", "changeTime"]),
173 /** @type {SyncHook<[]>} */
174 watchClose: new SyncHook([]),
175 /** @type {AsyncSeriesHook<[]>} */
176 shutdown: new AsyncSeriesHook([]),
177
178 /** @type {SyncBailHook<[string, string, any[]], true>} */
179 infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
180
181 // TODO the following hooks are weirdly located here
182 // TODO move them for webpack 5
183 /** @type {SyncHook<[]>} */
184 environment: new SyncHook([]),
185 /** @type {SyncHook<[]>} */
186 afterEnvironment: new SyncHook([]),
187 /** @type {SyncHook<[Compiler]>} */
188 afterPlugins: new SyncHook(["compiler"]),
189 /** @type {SyncHook<[Compiler]>} */
190 afterResolvers: new SyncHook(["compiler"]),
191 /** @type {SyncBailHook<[string, Entry], boolean>} */
192 entryOption: new SyncBailHook(["context", "entry"])
193 });
194
195 this.webpack = webpack;
196
197 /** @type {string=} */
198 this.name = undefined;
199 /** @type {Compilation=} */
200 this.parentCompilation = undefined;
201 /** @type {Compiler} */
202 this.root = this;
203 /** @type {string} */
204 this.outputPath = "";
205 /** @type {Watching} */
206 this.watching = undefined;
207
208 /** @type {OutputFileSystem} */
209 this.outputFileSystem = null;
210 /** @type {IntermediateFileSystem} */
211 this.intermediateFileSystem = null;
212 /** @type {InputFileSystem} */
213 this.inputFileSystem = null;
214 /** @type {WatchFileSystem} */
215 this.watchFileSystem = null;
216
217 /** @type {string|null} */
218 this.recordsInputPath = null;
219 /** @type {string|null} */
220 this.recordsOutputPath = null;
221 this.records = {};
222 /** @type {Set<string>} */
223 this.managedPaths = new Set();
224 /** @type {Set<string>} */
225 this.immutablePaths = new Set();
226
227 /** @type {ReadonlySet<string>} */
228 this.modifiedFiles = undefined;
229 /** @type {ReadonlySet<string>} */
230 this.removedFiles = undefined;
231 /** @type {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null>} */
232 this.fileTimestamps = undefined;
233 /** @type {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null>} */
234 this.contextTimestamps = undefined;
235 /** @type {number} */
236 this.fsStartTime = undefined;
237
238 /** @type {ResolverFactory} */
239 this.resolverFactory = new ResolverFactory();
240
241 this.infrastructureLogger = undefined;
242
243 /** @type {WebpackOptions} */
244 this.options = /** @type {WebpackOptions} */ ({});
245
246 this.context = context;
247
248 this.requestShortener = new RequestShortener(context, this.root);
249
250 this.cache = new Cache();
251
252 /** @type {Map<Module, { buildInfo: object, references: WeakMap<Dependency, Module>, memCache: WeakTupleMap }> | undefined} */
253 this.moduleMemCaches = undefined;
254
255 this.compilerPath = "";
256
257 /** @type {boolean} */
258 this.running = false;
259
260 /** @type {boolean} */
261 this.idle = false;
262
263 /** @type {boolean} */
264 this.watchMode = false;
265
266 /** @type {Compilation} */
267 this._lastCompilation = undefined;
268 /** @type {NormalModuleFactory} */
269 this._lastNormalModuleFactory = undefined;
270
271 /** @private @type {WeakMap<Source, { sizeOnlySource: SizeOnlySource, writtenTo: Map<string, number> }>} */
272 this._assetEmittingSourceCache = new WeakMap();
273 /** @private @type {Map<string, number>} */
274 this._assetEmittingWrittenFiles = new Map();
275 /** @private @type {Set<string>} */
276 this._assetEmittingPreviousFiles = new Set();
277 }
278
279 /**
280 * @param {string} name cache name
281 * @returns {CacheFacade} the cache facade instance
282 */
283 getCache(name) {
284 return new CacheFacade(
285 this.cache,
286 `${this.compilerPath}${name}`,
287 this.options.output.hashFunction
288 );
289 }
290
291 /**
292 * @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
293 * @returns {Logger} a logger with that name
294 */
295 getInfrastructureLogger(name) {
296 if (!name) {
297 throw new TypeError(
298 "Compiler.getInfrastructureLogger(name) called without a name"
299 );
300 }
301 return new Logger(
302 (type, args) => {
303 if (typeof name === "function") {
304 name = name();
305 if (!name) {
306 throw new TypeError(
307 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
308 );
309 }
310 }
311 if (this.hooks.infrastructureLog.call(name, type, args) === undefined) {
312 if (this.infrastructureLogger !== undefined) {
313 this.infrastructureLogger(name, type, args);
314 }
315 }
316 },
317 childName => {
318 if (typeof name === "function") {
319 if (typeof childName === "function") {
320 return this.getInfrastructureLogger(() => {
321 if (typeof name === "function") {
322 name = name();
323 if (!name) {
324 throw new TypeError(
325 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
326 );
327 }
328 }
329 if (typeof childName === "function") {
330 childName = childName();
331 if (!childName) {
332 throw new TypeError(
333 "Logger.getChildLogger(name) called with a function not returning a name"
334 );
335 }
336 }
337 return `${name}/${childName}`;
338 });
339 } else {
340 return this.getInfrastructureLogger(() => {
341 if (typeof name === "function") {
342 name = name();
343 if (!name) {
344 throw new TypeError(
345 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
346 );
347 }
348 }
349 return `${name}/${childName}`;
350 });
351 }
352 } else {
353 if (typeof childName === "function") {
354 return this.getInfrastructureLogger(() => {
355 if (typeof childName === "function") {
356 childName = childName();
357 if (!childName) {
358 throw new TypeError(
359 "Logger.getChildLogger(name) called with a function not returning a name"
360 );
361 }
362 }
363 return `${name}/${childName}`;
364 });
365 } else {
366 return this.getInfrastructureLogger(`${name}/${childName}`);
367 }
368 }
369 }
370 );
371 }
372
373 // TODO webpack 6: solve this in a better way
374 // e.g. move compilation specific info from Modules into ModuleGraph
375 _cleanupLastCompilation() {
376 if (this._lastCompilation !== undefined) {
377 for (const module of this._lastCompilation.modules) {
378 ChunkGraph.clearChunkGraphForModule(module);
379 ModuleGraph.clearModuleGraphForModule(module);
380 module.cleanupForCache();
381 }
382 for (const chunk of this._lastCompilation.chunks) {
383 ChunkGraph.clearChunkGraphForChunk(chunk);
384 }
385 this._lastCompilation = undefined;
386 }
387 }
388
389 // TODO webpack 6: solve this in a better way
390 _cleanupLastNormalModuleFactory() {
391 if (this._lastNormalModuleFactory !== undefined) {
392 this._lastNormalModuleFactory.cleanupForCache();
393 this._lastNormalModuleFactory = undefined;
394 }
395 }
396
397 /**
398 * @param {WatchOptions} watchOptions the watcher's options
399 * @param {Callback<Stats>} handler signals when the call finishes
400 * @returns {Watching} a compiler watcher
401 */
402 watch(watchOptions, handler) {
403 if (this.running) {
404 return handler(new ConcurrentCompilationError());
405 }
406
407 this.running = true;
408 this.watchMode = true;
409 this.watching = new Watching(this, watchOptions, handler);
410 return this.watching;
411 }
412
413 /**
414 * @param {Callback<Stats>} callback signals when the call finishes
415 * @returns {void}
416 */
417 run(callback) {
418 if (this.running) {
419 return callback(new ConcurrentCompilationError());
420 }
421
422 let logger;
423
424 const finalCallback = (err, stats) => {
425 if (logger) logger.time("beginIdle");
426 this.idle = true;
427 this.cache.beginIdle();
428 this.idle = true;
429 if (logger) logger.timeEnd("beginIdle");
430 this.running = false;
431 if (err) {
432 this.hooks.failed.call(err);
433 }
434 if (callback !== undefined) callback(err, stats);
435 this.hooks.afterDone.call(stats);
436 };
437
438 const startTime = Date.now();
439
440 this.running = true;
441
442 const onCompiled = (err, compilation) => {
443 if (err) return finalCallback(err);
444
445 if (this.hooks.shouldEmit.call(compilation) === false) {
446 compilation.startTime = startTime;
447 compilation.endTime = Date.now();
448 const stats = new Stats(compilation);
449 this.hooks.done.callAsync(stats, err => {
450 if (err) return finalCallback(err);
451 return finalCallback(null, stats);
452 });
453 return;
454 }
455
456 process.nextTick(() => {
457 logger = compilation.getLogger("webpack.Compiler");
458 logger.time("emitAssets");
459 this.emitAssets(compilation, err => {
460 logger.timeEnd("emitAssets");
461 if (err) return finalCallback(err);
462
463 if (compilation.hooks.needAdditionalPass.call()) {
464 compilation.needAdditionalPass = true;
465
466 compilation.startTime = startTime;
467 compilation.endTime = Date.now();
468 logger.time("done hook");
469 const stats = new Stats(compilation);
470 this.hooks.done.callAsync(stats, err => {
471 logger.timeEnd("done hook");
472 if (err) return finalCallback(err);
473
474 this.hooks.additionalPass.callAsync(err => {
475 if (err) return finalCallback(err);
476 this.compile(onCompiled);
477 });
478 });
479 return;
480 }
481
482 logger.time("emitRecords");
483 this.emitRecords(err => {
484 logger.timeEnd("emitRecords");
485 if (err) return finalCallback(err);
486
487 compilation.startTime = startTime;
488 compilation.endTime = Date.now();
489 logger.time("done hook");
490 const stats = new Stats(compilation);
491 this.hooks.done.callAsync(stats, err => {
492 logger.timeEnd("done hook");
493 if (err) return finalCallback(err);
494 this.cache.storeBuildDependencies(
495 compilation.buildDependencies,
496 err => {
497 if (err) return finalCallback(err);
498 return finalCallback(null, stats);
499 }
500 );
501 });
502 });
503 });
504 });
505 };
506
507 const run = () => {
508 this.hooks.beforeRun.callAsync(this, err => {
509 if (err) return finalCallback(err);
510
511 this.hooks.run.callAsync(this, err => {
512 if (err) return finalCallback(err);
513
514 this.readRecords(err => {
515 if (err) return finalCallback(err);
516
517 this.compile(onCompiled);
518 });
519 });
520 });
521 };
522
523 if (this.idle) {
524 this.cache.endIdle(err => {
525 if (err) return finalCallback(err);
526
527 this.idle = false;
528 run();
529 });
530 } else {
531 run();
532 }
533 }
534
535 /**
536 * @param {RunAsChildCallback} callback signals when the call finishes
537 * @returns {void}
538 */
539 runAsChild(callback) {
540 const startTime = Date.now();
541 this.compile((err, compilation) => {
542 if (err) return callback(err);
543
544 this.parentCompilation.children.push(compilation);
545 for (const { name, source, info } of compilation.getAssets()) {
546 this.parentCompilation.emitAsset(name, source, info);
547 }
548
549 const entries = [];
550 for (const ep of compilation.entrypoints.values()) {
551 entries.push(...ep.chunks);
552 }
553
554 compilation.startTime = startTime;
555 compilation.endTime = Date.now();
556
557 return callback(null, entries, compilation);
558 });
559 }
560
561 purgeInputFileSystem() {
562 if (this.inputFileSystem && this.inputFileSystem.purge) {
563 this.inputFileSystem.purge();
564 }
565 }
566
567 /**
568 * @param {Compilation} compilation the compilation
569 * @param {Callback<void>} callback signals when the assets are emitted
570 * @returns {void}
571 */
572 emitAssets(compilation, callback) {
573 let outputPath;
574
575 const emitFiles = err => {
576 if (err) return callback(err);
577
578 const assets = compilation.getAssets();
579 compilation.assets = { ...compilation.assets };
580 /** @type {Map<string, { path: string, source: Source, size: number, waiting: { cacheEntry: any, file: string }[] }>} */
581 const caseInsensitiveMap = new Map();
582 /** @type {Set<string>} */
583 const allTargetPaths = new Set();
584 asyncLib.forEachLimit(
585 assets,
586 15,
587 ({ name: file, source, info }, callback) => {
588 let targetFile = file;
589 let immutable = info.immutable;
590 const queryStringIdx = targetFile.indexOf("?");
591 if (queryStringIdx >= 0) {
592 targetFile = targetFile.substr(0, queryStringIdx);
593 // We may remove the hash, which is in the query string
594 // So we recheck if the file is immutable
595 // This doesn't cover all cases, but immutable is only a performance optimization anyway
596 immutable =
597 immutable &&
598 (includesHash(targetFile, info.contenthash) ||
599 includesHash(targetFile, info.chunkhash) ||
600 includesHash(targetFile, info.modulehash) ||
601 includesHash(targetFile, info.fullhash));
602 }
603
604 const writeOut = err => {
605 if (err) return callback(err);
606 const targetPath = join(
607 this.outputFileSystem,
608 outputPath,
609 targetFile
610 );
611 allTargetPaths.add(targetPath);
612
613 // check if the target file has already been written by this Compiler
614 const targetFileGeneration =
615 this._assetEmittingWrittenFiles.get(targetPath);
616
617 // create an cache entry for this Source if not already existing
618 let cacheEntry = this._assetEmittingSourceCache.get(source);
619 if (cacheEntry === undefined) {
620 cacheEntry = {
621 sizeOnlySource: undefined,
622 writtenTo: new Map()
623 };
624 this._assetEmittingSourceCache.set(source, cacheEntry);
625 }
626
627 let similarEntry;
628
629 const checkSimilarFile = () => {
630 const caseInsensitiveTargetPath = targetPath.toLowerCase();
631 similarEntry = caseInsensitiveMap.get(caseInsensitiveTargetPath);
632 if (similarEntry !== undefined) {
633 const { path: other, source: otherSource } = similarEntry;
634 if (isSourceEqual(otherSource, source)) {
635 // Size may or may not be available at this point.
636 // If it's not available add to "waiting" list and it will be updated once available
637 if (similarEntry.size !== undefined) {
638 updateWithReplacementSource(similarEntry.size);
639 } else {
640 if (!similarEntry.waiting) similarEntry.waiting = [];
641 similarEntry.waiting.push({ file, cacheEntry });
642 }
643 alreadyWritten();
644 } else {
645 const err =
646 new WebpackError(`Prevent writing to file that only differs in casing or query string from already written file.
647This will lead to a race-condition and corrupted files on case-insensitive file systems.
648${targetPath}
649${other}`);
650 err.file = file;
651 callback(err);
652 }
653 return true;
654 } else {
655 caseInsensitiveMap.set(
656 caseInsensitiveTargetPath,
657 (similarEntry = {
658 path: targetPath,
659 source,
660 size: undefined,
661 waiting: undefined
662 })
663 );
664 return false;
665 }
666 };
667
668 /**
669 * get the binary (Buffer) content from the Source
670 * @returns {Buffer} content for the source
671 */
672 const getContent = () => {
673 if (typeof source.buffer === "function") {
674 return source.buffer();
675 } else {
676 const bufferOrString = source.source();
677 if (Buffer.isBuffer(bufferOrString)) {
678 return bufferOrString;
679 } else {
680 return Buffer.from(bufferOrString, "utf8");
681 }
682 }
683 };
684
685 const alreadyWritten = () => {
686 // cache the information that the Source has been already been written to that location
687 if (targetFileGeneration === undefined) {
688 const newGeneration = 1;
689 this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
690 cacheEntry.writtenTo.set(targetPath, newGeneration);
691 } else {
692 cacheEntry.writtenTo.set(targetPath, targetFileGeneration);
693 }
694 callback();
695 };
696
697 /**
698 * Write the file to output file system
699 * @param {Buffer} content content to be written
700 * @returns {void}
701 */
702 const doWrite = content => {
703 this.outputFileSystem.writeFile(targetPath, content, err => {
704 if (err) return callback(err);
705
706 // information marker that the asset has been emitted
707 compilation.emittedAssets.add(file);
708
709 // cache the information that the Source has been written to that location
710 const newGeneration =
711 targetFileGeneration === undefined
712 ? 1
713 : targetFileGeneration + 1;
714 cacheEntry.writtenTo.set(targetPath, newGeneration);
715 this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
716 this.hooks.assetEmitted.callAsync(
717 file,
718 {
719 content,
720 source,
721 outputPath,
722 compilation,
723 targetPath
724 },
725 callback
726 );
727 });
728 };
729
730 const updateWithReplacementSource = size => {
731 updateFileWithReplacementSource(file, cacheEntry, size);
732 similarEntry.size = size;
733 if (similarEntry.waiting !== undefined) {
734 for (const { file, cacheEntry } of similarEntry.waiting) {
735 updateFileWithReplacementSource(file, cacheEntry, size);
736 }
737 }
738 };
739
740 const updateFileWithReplacementSource = (
741 file,
742 cacheEntry,
743 size
744 ) => {
745 // Create a replacement resource which only allows to ask for size
746 // This allows to GC all memory allocated by the Source
747 // (expect when the Source is stored in any other cache)
748 if (!cacheEntry.sizeOnlySource) {
749 cacheEntry.sizeOnlySource = new SizeOnlySource(size);
750 }
751 compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
752 size
753 });
754 };
755
756 const processExistingFile = stats => {
757 // skip emitting if it's already there and an immutable file
758 if (immutable) {
759 updateWithReplacementSource(stats.size);
760 return alreadyWritten();
761 }
762
763 const content = getContent();
764
765 updateWithReplacementSource(content.length);
766
767 // if it exists and content on disk matches content
768 // skip writing the same content again
769 // (to keep mtime and don't trigger watchers)
770 // for a fast negative match file size is compared first
771 if (content.length === stats.size) {
772 compilation.comparedForEmitAssets.add(file);
773 return this.outputFileSystem.readFile(
774 targetPath,
775 (err, existingContent) => {
776 if (
777 err ||
778 !content.equals(/** @type {Buffer} */ (existingContent))
779 ) {
780 return doWrite(content);
781 } else {
782 return alreadyWritten();
783 }
784 }
785 );
786 }
787
788 return doWrite(content);
789 };
790
791 const processMissingFile = () => {
792 const content = getContent();
793
794 updateWithReplacementSource(content.length);
795
796 return doWrite(content);
797 };
798
799 // if the target file has already been written
800 if (targetFileGeneration !== undefined) {
801 // check if the Source has been written to this target file
802 const writtenGeneration = cacheEntry.writtenTo.get(targetPath);
803 if (writtenGeneration === targetFileGeneration) {
804 // if yes, we may skip writing the file
805 // if it's already there
806 // (we assume one doesn't modify files while the Compiler is running, other then removing them)
807
808 if (this._assetEmittingPreviousFiles.has(targetPath)) {
809 // We assume that assets from the last compilation say intact on disk (they are not removed)
810 compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
811 size: cacheEntry.sizeOnlySource.size()
812 });
813
814 return callback();
815 } else {
816 // Settings immutable will make it accept file content without comparing when file exist
817 immutable = true;
818 }
819 } else if (!immutable) {
820 if (checkSimilarFile()) return;
821 // We wrote to this file before which has very likely a different content
822 // skip comparing and assume content is different for performance
823 // This case happens often during watch mode.
824 return processMissingFile();
825 }
826 }
827
828 if (checkSimilarFile()) return;
829 if (this.options.output.compareBeforeEmit) {
830 this.outputFileSystem.stat(targetPath, (err, stats) => {
831 const exists = !err && stats.isFile();
832
833 if (exists) {
834 processExistingFile(stats);
835 } else {
836 processMissingFile();
837 }
838 });
839 } else {
840 processMissingFile();
841 }
842 };
843
844 if (targetFile.match(/\/|\\/)) {
845 const fs = this.outputFileSystem;
846 const dir = dirname(fs, join(fs, outputPath, targetFile));
847 mkdirp(fs, dir, writeOut);
848 } else {
849 writeOut();
850 }
851 },
852 err => {
853 // Clear map to free up memory
854 caseInsensitiveMap.clear();
855 if (err) {
856 this._assetEmittingPreviousFiles.clear();
857 return callback(err);
858 }
859
860 this._assetEmittingPreviousFiles = allTargetPaths;
861
862 this.hooks.afterEmit.callAsync(compilation, err => {
863 if (err) return callback(err);
864
865 return callback();
866 });
867 }
868 );
869 };
870
871 this.hooks.emit.callAsync(compilation, err => {
872 if (err) return callback(err);
873 outputPath = compilation.getPath(this.outputPath, {});
874 mkdirp(this.outputFileSystem, outputPath, emitFiles);
875 });
876 }
877
878 /**
879 * @param {Callback<void>} callback signals when the call finishes
880 * @returns {void}
881 */
882 emitRecords(callback) {
883 if (!this.recordsOutputPath) return callback();
884
885 const writeFile = () => {
886 this.outputFileSystem.writeFile(
887 this.recordsOutputPath,
888 JSON.stringify(
889 this.records,
890 (n, value) => {
891 if (
892 typeof value === "object" &&
893 value !== null &&
894 !Array.isArray(value)
895 ) {
896 const keys = Object.keys(value);
897 if (!isSorted(keys)) {
898 return sortObject(value, keys);
899 }
900 }
901 return value;
902 },
903 2
904 ),
905 callback
906 );
907 };
908
909 const recordsOutputPathDirectory = dirname(
910 this.outputFileSystem,
911 this.recordsOutputPath
912 );
913 if (!recordsOutputPathDirectory) {
914 return writeFile();
915 }
916 mkdirp(this.outputFileSystem, recordsOutputPathDirectory, err => {
917 if (err) return callback(err);
918 writeFile();
919 });
920 }
921
922 /**
923 * @param {Callback<void>} callback signals when the call finishes
924 * @returns {void}
925 */
926 readRecords(callback) {
927 if (!this.recordsInputPath) {
928 this.records = {};
929 return callback();
930 }
931 this.inputFileSystem.stat(this.recordsInputPath, err => {
932 // It doesn't exist
933 // We can ignore this.
934 if (err) return callback();
935
936 this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
937 if (err) return callback(err);
938
939 try {
940 this.records = parseJson(content.toString("utf-8"));
941 } catch (e) {
942 e.message = "Cannot parse records: " + e.message;
943 return callback(e);
944 }
945
946 return callback();
947 });
948 });
949 }
950
951 /**
952 * @param {Compilation} compilation the compilation
953 * @param {string} compilerName the compiler's name
954 * @param {number} compilerIndex the compiler's index
955 * @param {OutputOptions=} outputOptions the output options
956 * @param {WebpackPluginInstance[]=} plugins the plugins to apply
957 * @returns {Compiler} a child compiler
958 */
959 createChildCompiler(
960 compilation,
961 compilerName,
962 compilerIndex,
963 outputOptions,
964 plugins
965 ) {
966 const childCompiler = new Compiler(this.context);
967 childCompiler.name = compilerName;
968 childCompiler.outputPath = this.outputPath;
969 childCompiler.inputFileSystem = this.inputFileSystem;
970 childCompiler.outputFileSystem = null;
971 childCompiler.resolverFactory = this.resolverFactory;
972 childCompiler.modifiedFiles = this.modifiedFiles;
973 childCompiler.removedFiles = this.removedFiles;
974 childCompiler.fileTimestamps = this.fileTimestamps;
975 childCompiler.contextTimestamps = this.contextTimestamps;
976 childCompiler.fsStartTime = this.fsStartTime;
977 childCompiler.cache = this.cache;
978 childCompiler.compilerPath = `${this.compilerPath}${compilerName}|${compilerIndex}|`;
979
980 const relativeCompilerName = makePathsRelative(
981 this.context,
982 compilerName,
983 this.root
984 );
985 if (!this.records[relativeCompilerName]) {
986 this.records[relativeCompilerName] = [];
987 }
988 if (this.records[relativeCompilerName][compilerIndex]) {
989 childCompiler.records = this.records[relativeCompilerName][compilerIndex];
990 } else {
991 this.records[relativeCompilerName].push((childCompiler.records = {}));
992 }
993
994 childCompiler.options = {
995 ...this.options,
996 output: {
997 ...this.options.output,
998 ...outputOptions
999 }
1000 };
1001 childCompiler.parentCompilation = compilation;
1002 childCompiler.root = this.root;
1003 if (Array.isArray(plugins)) {
1004 for (const plugin of plugins) {
1005 plugin.apply(childCompiler);
1006 }
1007 }
1008 for (const name in this.hooks) {
1009 if (
1010 ![
1011 "make",
1012 "compile",
1013 "emit",
1014 "afterEmit",
1015 "invalid",
1016 "done",
1017 "thisCompilation"
1018 ].includes(name)
1019 ) {
1020 if (childCompiler.hooks[name]) {
1021 childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
1022 }
1023 }
1024 }
1025
1026 compilation.hooks.childCompiler.call(
1027 childCompiler,
1028 compilerName,
1029 compilerIndex
1030 );
1031
1032 return childCompiler;
1033 }
1034
1035 isChild() {
1036 return !!this.parentCompilation;
1037 }
1038
1039 createCompilation(params) {
1040 this._cleanupLastCompilation();
1041 return (this._lastCompilation = new Compilation(this, params));
1042 }
1043
1044 /**
1045 * @param {CompilationParams} params the compilation parameters
1046 * @returns {Compilation} the created compilation
1047 */
1048 newCompilation(params) {
1049 const compilation = this.createCompilation(params);
1050 compilation.name = this.name;
1051 compilation.records = this.records;
1052 this.hooks.thisCompilation.call(compilation, params);
1053 this.hooks.compilation.call(compilation, params);
1054 return compilation;
1055 }
1056
1057 createNormalModuleFactory() {
1058 this._cleanupLastNormalModuleFactory();
1059 const normalModuleFactory = new NormalModuleFactory({
1060 context: this.options.context,
1061 fs: this.inputFileSystem,
1062 resolverFactory: this.resolverFactory,
1063 options: this.options.module,
1064 associatedObjectForCache: this.root,
1065 layers: this.options.experiments.layers
1066 });
1067 this._lastNormalModuleFactory = normalModuleFactory;
1068 this.hooks.normalModuleFactory.call(normalModuleFactory);
1069 return normalModuleFactory;
1070 }
1071
1072 createContextModuleFactory() {
1073 const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
1074 this.hooks.contextModuleFactory.call(contextModuleFactory);
1075 return contextModuleFactory;
1076 }
1077
1078 newCompilationParams() {
1079 const params = {
1080 normalModuleFactory: this.createNormalModuleFactory(),
1081 contextModuleFactory: this.createContextModuleFactory()
1082 };
1083 return params;
1084 }
1085
1086 /**
1087 * @param {Callback<Compilation>} callback signals when the compilation finishes
1088 * @returns {void}
1089 */
1090 compile(callback) {
1091 const params = this.newCompilationParams();
1092 this.hooks.beforeCompile.callAsync(params, err => {
1093 if (err) return callback(err);
1094
1095 this.hooks.compile.call(params);
1096
1097 const compilation = this.newCompilation(params);
1098
1099 const logger = compilation.getLogger("webpack.Compiler");
1100
1101 logger.time("make hook");
1102 this.hooks.make.callAsync(compilation, err => {
1103 logger.timeEnd("make hook");
1104 if (err) return callback(err);
1105
1106 logger.time("finish make hook");
1107 this.hooks.finishMake.callAsync(compilation, err => {
1108 logger.timeEnd("finish make hook");
1109 if (err) return callback(err);
1110
1111 process.nextTick(() => {
1112 logger.time("finish compilation");
1113 compilation.finish(err => {
1114 logger.timeEnd("finish compilation");
1115 if (err) return callback(err);
1116
1117 logger.time("seal compilation");
1118 compilation.seal(err => {
1119 logger.timeEnd("seal compilation");
1120 if (err) return callback(err);
1121
1122 logger.time("afterCompile hook");
1123 this.hooks.afterCompile.callAsync(compilation, err => {
1124 logger.timeEnd("afterCompile hook");
1125 if (err) return callback(err);
1126
1127 return callback(null, compilation);
1128 });
1129 });
1130 });
1131 });
1132 });
1133 });
1134 });
1135 }
1136
1137 /**
1138 * @param {Callback<void>} callback signals when the compiler closes
1139 * @returns {void}
1140 */
1141 close(callback) {
1142 if (this.watching) {
1143 // When there is still an active watching, close this first
1144 this.watching.close(err => {
1145 this.close(callback);
1146 });
1147 return;
1148 }
1149 this.hooks.shutdown.callAsync(err => {
1150 if (err) return callback(err);
1151 // Get rid of reference to last compilation to avoid leaking memory
1152 // We can't run this._cleanupLastCompilation() as the Stats to this compilation
1153 // might be still in use. We try to get rid of the reference to the cache instead.
1154 this._lastCompilation = undefined;
1155 this._lastNormalModuleFactory = undefined;
1156 this.cache.shutdown(callback);
1157 });
1158 }
1159}
1160
1161module.exports = Compiler;