simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

Compiler.js (35866B)


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