| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const parseJson = require("json-parse-better-errors");const asyncLib = require("neo-async");const path = require("path");const { Source } = require("webpack-sources");const util = require("util");const {	Tapable,	SyncHook,	SyncBailHook,	AsyncParallelHook,	AsyncSeriesHook} = require("tapable");const Compilation = require("./Compilation");const Stats = require("./Stats");const Watching = require("./Watching");const NormalModuleFactory = require("./NormalModuleFactory");const ContextModuleFactory = require("./ContextModuleFactory");const ResolverFactory = require("./ResolverFactory");const RequestShortener = require("./RequestShortener");const { makePathsRelative } = require("./util/identifier");const ConcurrentCompilationError = require("./ConcurrentCompilationError");const { Logger } = require("./logging/Logger");/** @typedef {import("../declarations/WebpackOptions").Entry} Entry *//** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions *//** * @typedef {Object} CompilationParams * @property {NormalModuleFactory} normalModuleFactory * @property {ContextModuleFactory} contextModuleFactory * @property {Set<string>} compilationDependencies */class Compiler extends Tapable {	constructor(context) {		super();		this.hooks = {			/** @type {SyncBailHook<Compilation>} */			shouldEmit: new SyncBailHook(["compilation"]),			/** @type {AsyncSeriesHook<Stats>} */			done: new AsyncSeriesHook(["stats"]),			/** @type {AsyncSeriesHook<>} */			additionalPass: new AsyncSeriesHook([]),			/** @type {AsyncSeriesHook<Compiler>} */			beforeRun: new AsyncSeriesHook(["compiler"]),			/** @type {AsyncSeriesHook<Compiler>} */			run: new AsyncSeriesHook(["compiler"]),			/** @type {AsyncSeriesHook<Compilation>} */			emit: new AsyncSeriesHook(["compilation"]),			/** @type {AsyncSeriesHook<string, Buffer>} */			assetEmitted: new AsyncSeriesHook(["file", "content"]),			/** @type {AsyncSeriesHook<Compilation>} */			afterEmit: new AsyncSeriesHook(["compilation"]),			/** @type {SyncHook<Compilation, CompilationParams>} */			thisCompilation: new SyncHook(["compilation", "params"]),			/** @type {SyncHook<Compilation, CompilationParams>} */			compilation: new SyncHook(["compilation", "params"]),			/** @type {SyncHook<NormalModuleFactory>} */			normalModuleFactory: new SyncHook(["normalModuleFactory"]),			/** @type {SyncHook<ContextModuleFactory>}  */			contextModuleFactory: new SyncHook(["contextModulefactory"]),			/** @type {AsyncSeriesHook<CompilationParams>} */			beforeCompile: new AsyncSeriesHook(["params"]),			/** @type {SyncHook<CompilationParams>} */			compile: new SyncHook(["params"]),			/** @type {AsyncParallelHook<Compilation>} */			make: new AsyncParallelHook(["compilation"]),			/** @type {AsyncSeriesHook<Compilation>} */			afterCompile: new AsyncSeriesHook(["compilation"]),			/** @type {AsyncSeriesHook<Compiler>} */			watchRun: new AsyncSeriesHook(["compiler"]),			/** @type {SyncHook<Error>} */			failed: new SyncHook(["error"]),			/** @type {SyncHook<string, string>} */			invalid: new SyncHook(["filename", "changeTime"]),			/** @type {SyncHook} */			watchClose: new SyncHook([]),			/** @type {SyncBailHook<string, string, any[]>} */			infrastructureLog: new SyncBailHook(["origin", "type", "args"]),			// TODO the following hooks are weirdly located here			// TODO move them for webpack 5			/** @type {SyncHook} */			environment: new SyncHook([]),			/** @type {SyncHook} */			afterEnvironment: new SyncHook([]),			/** @type {SyncHook<Compiler>} */			afterPlugins: new SyncHook(["compiler"]),			/** @type {SyncHook<Compiler>} */			afterResolvers: new SyncHook(["compiler"]),			/** @type {SyncBailHook<string, Entry>} */			entryOption: new SyncBailHook(["context", "entry"])		};		// TODO webpack 5 remove this		this.hooks.infrastructurelog = this.hooks.infrastructureLog;		this._pluginCompat.tap("Compiler", options => {			switch (options.name) {				case "additional-pass":				case "before-run":				case "run":				case "emit":				case "after-emit":				case "before-compile":				case "make":				case "after-compile":				case "watch-run":					options.async = true;					break;			}		});		/** @type {string=} */		this.name = undefined;		/** @type {Compilation=} */		this.parentCompilation = undefined;		/** @type {string} */		this.outputPath = "";		this.outputFileSystem = null;		this.inputFileSystem = null;		/** @type {string|null} */		this.recordsInputPath = null;		/** @type {string|null} */		this.recordsOutputPath = null;		this.records = {};		this.removedFiles = new Set();		/** @type {Map<string, number>} */		this.fileTimestamps = new Map();		/** @type {Map<string, number>} */		this.contextTimestamps = new Map();		/** @type {ResolverFactory} */		this.resolverFactory = new ResolverFactory();		this.infrastructureLogger = undefined;		// TODO remove in webpack 5		this.resolvers = {			normal: {				plugins: util.deprecate((hook, fn) => {					this.resolverFactory.plugin("resolver normal", resolver => {						resolver.plugin(hook, fn);					});				}, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),				apply: util.deprecate((...args) => {					this.resolverFactory.plugin("resolver normal", resolver => {						resolver.apply(...args);					});				}, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n  resolver.apply(/* … */);\n}); instead.')			},			loader: {				plugins: util.deprecate((hook, fn) => {					this.resolverFactory.plugin("resolver loader", resolver => {						resolver.plugin(hook, fn);					});				}, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),				apply: util.deprecate((...args) => {					this.resolverFactory.plugin("resolver loader", resolver => {						resolver.apply(...args);					});				}, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n  resolver.apply(/* … */);\n}); instead.')			},			context: {				plugins: util.deprecate((hook, fn) => {					this.resolverFactory.plugin("resolver context", resolver => {						resolver.plugin(hook, fn);					});				}, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n  resolver.plugin(/* … */);\n}); instead.'),				apply: util.deprecate((...args) => {					this.resolverFactory.plugin("resolver context", resolver => {						resolver.apply(...args);					});				}, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n  resolver.apply(/* … */);\n}); instead.')			}		};		/** @type {WebpackOptions} */		this.options = /** @type {WebpackOptions} */ ({});		this.context = context;		this.requestShortener = new RequestShortener(context);		/** @type {boolean} */		this.running = false;		/** @type {boolean} */		this.watchMode = false;		/** @private @type {WeakMap<Source, { sizeOnlySource: SizeOnlySource, writtenTo: Map<string, number> }>} */		this._assetEmittingSourceCache = new WeakMap();		/** @private @type {Map<string, number>} */		this._assetEmittingWrittenFiles = new Map();	}	/**	 * @param {string | (function(): string)} name name of the logger, or function called once to get the logger name	 * @returns {Logger} a logger with that name	 */	getInfrastructureLogger(name) {		if (!name) {			throw new TypeError(				"Compiler.getInfrastructureLogger(name) called without a name"			);		}		return new Logger((type, args) => {			if (typeof name === "function") {				name = name();				if (!name) {					throw new TypeError(						"Compiler.getInfrastructureLogger(name) called with a function not returning a name"					);				}			}			if (this.hooks.infrastructureLog.call(name, type, args) === undefined) {				if (this.infrastructureLogger !== undefined) {					this.infrastructureLogger(name, type, args);				}			}		});	}	watch(watchOptions, handler) {		if (this.running) return handler(new ConcurrentCompilationError());		this.running = true;		this.watchMode = true;		this.fileTimestamps = new Map();		this.contextTimestamps = new Map();		this.removedFiles = new Set();		return new Watching(this, watchOptions, handler);	}	run(callback) {		if (this.running) return callback(new ConcurrentCompilationError());		const finalCallback = (err, stats) => {			this.running = false;			if (err) {				this.hooks.failed.call(err);			}			if (callback !== undefined) return callback(err, stats);		};		const startTime = Date.now();		this.running = true;		const onCompiled = (err, compilation) => {			if (err) return finalCallback(err);			if (this.hooks.shouldEmit.call(compilation) === false) {				const stats = new Stats(compilation);				stats.startTime = startTime;				stats.endTime = Date.now();				this.hooks.done.callAsync(stats, err => {					if (err) return finalCallback(err);					return finalCallback(null, stats);				});				return;			}			this.emitAssets(compilation, err => {				if (err) return finalCallback(err);				if (compilation.hooks.needAdditionalPass.call()) {					compilation.needAdditionalPass = true;					const stats = new Stats(compilation);					stats.startTime = startTime;					stats.endTime = Date.now();					this.hooks.done.callAsync(stats, err => {						if (err) return finalCallback(err);						this.hooks.additionalPass.callAsync(err => {							if (err) return finalCallback(err);							this.compile(onCompiled);						});					});					return;				}				this.emitRecords(err => {					if (err) return finalCallback(err);					const stats = new Stats(compilation);					stats.startTime = startTime;					stats.endTime = Date.now();					this.hooks.done.callAsync(stats, err => {						if (err) return finalCallback(err);						return finalCallback(null, stats);					});				});			});		};		this.hooks.beforeRun.callAsync(this, err => {			if (err) return finalCallback(err);			this.hooks.run.callAsync(this, err => {				if (err) return finalCallback(err);				this.readRecords(err => {					if (err) return finalCallback(err);					this.compile(onCompiled);				});			});		});	}	runAsChild(callback) {		this.compile((err, compilation) => {			if (err) return callback(err);			this.parentCompilation.children.push(compilation);			for (const { name, source, info } of compilation.getAssets()) {				this.parentCompilation.emitAsset(name, source, info);			}			const entries = Array.from(				compilation.entrypoints.values(),				ep => ep.chunks			).reduce((array, chunks) => {				return array.concat(chunks);			}, []);			return callback(null, entries, compilation);		});	}	purgeInputFileSystem() {		if (this.inputFileSystem && this.inputFileSystem.purge) {			this.inputFileSystem.purge();		}	}	emitAssets(compilation, callback) {		let outputPath;		const emitFiles = err => {			if (err) return callback(err);			asyncLib.forEachLimit(				compilation.getAssets(),				15,				({ name: file, source }, callback) => {					let targetFile = file;					const queryStringIdx = targetFile.indexOf("?");					if (queryStringIdx >= 0) {						targetFile = targetFile.substr(0, queryStringIdx);					}					const writeOut = err => {						if (err) return callback(err);						const targetPath = this.outputFileSystem.join(							outputPath,							targetFile						);						// TODO webpack 5 remove futureEmitAssets option and make it on by default						if (this.options.output.futureEmitAssets) {							// check if the target file has already been written by this Compiler							const targetFileGeneration = this._assetEmittingWrittenFiles.get(								targetPath							);							// create an cache entry for this Source if not already existing							let cacheEntry = this._assetEmittingSourceCache.get(source);							if (cacheEntry === undefined) {								cacheEntry = {									sizeOnlySource: undefined,									writtenTo: new Map()								};								this._assetEmittingSourceCache.set(source, cacheEntry);							}							// if the target file has already been written							if (targetFileGeneration !== undefined) {								// check if the Source has been written to this target file								const writtenGeneration = cacheEntry.writtenTo.get(targetPath);								if (writtenGeneration === targetFileGeneration) {									// if yes, we skip writing the file									// as it's already there									// (we assume one doesn't remove files while the Compiler is running)									compilation.updateAsset(file, cacheEntry.sizeOnlySource, {										size: cacheEntry.sizeOnlySource.size()									});									return callback();								}							}							// TODO webpack 5: if info.immutable check if file already exists in output							// skip emitting if it's already there							// get the binary (Buffer) content from the Source							/** @type {Buffer} */							let content;							if (typeof source.buffer === "function") {								content = source.buffer();							} else {								const bufferOrString = source.source();								if (Buffer.isBuffer(bufferOrString)) {									content = bufferOrString;								} else {									content = Buffer.from(bufferOrString, "utf8");								}							}							// Create a replacement resource which only allows to ask for size							// This allows to GC all memory allocated by the Source							// (expect when the Source is stored in any other cache)							cacheEntry.sizeOnlySource = new SizeOnlySource(content.length);							compilation.updateAsset(file, cacheEntry.sizeOnlySource, {								size: content.length							});							// Write the file to output file system							this.outputFileSystem.writeFile(targetPath, content, err => {								if (err) return callback(err);								// information marker that the asset has been emitted								compilation.emittedAssets.add(file);								// cache the information that the Source has been written to that location								const newGeneration =									targetFileGeneration === undefined										? 1										: targetFileGeneration + 1;								cacheEntry.writtenTo.set(targetPath, newGeneration);								this._assetEmittingWrittenFiles.set(targetPath, newGeneration);								this.hooks.assetEmitted.callAsync(file, content, callback);							});						} else {							if (source.existsAt === targetPath) {								source.emitted = false;								return callback();							}							let content = source.source();							if (!Buffer.isBuffer(content)) {								content = Buffer.from(content, "utf8");							}							source.existsAt = targetPath;							source.emitted = true;							this.outputFileSystem.writeFile(targetPath, content, err => {								if (err) return callback(err);								this.hooks.assetEmitted.callAsync(file, content, callback);							});						}					};					if (targetFile.match(/\/|\\/)) {						const dir = path.dirname(targetFile);						this.outputFileSystem.mkdirp(							this.outputFileSystem.join(outputPath, dir),							writeOut						);					} else {						writeOut();					}				},				err => {					if (err) return callback(err);					this.hooks.afterEmit.callAsync(compilation, err => {						if (err) return callback(err);						return callback();					});				}			);		};		this.hooks.emit.callAsync(compilation, err => {			if (err) return callback(err);			outputPath = compilation.getPath(this.outputPath);			this.outputFileSystem.mkdirp(outputPath, emitFiles);		});	}	emitRecords(callback) {		if (!this.recordsOutputPath) return callback();		const idx1 = this.recordsOutputPath.lastIndexOf("/");		const idx2 = this.recordsOutputPath.lastIndexOf("\\");		let recordsOutputPathDirectory = null;		if (idx1 > idx2) {			recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx1);		} else if (idx1 < idx2) {			recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx2);		}		const writeFile = () => {			this.outputFileSystem.writeFile(				this.recordsOutputPath,				JSON.stringify(this.records, undefined, 2),				callback			);		};		if (!recordsOutputPathDirectory) {			return writeFile();		}		this.outputFileSystem.mkdirp(recordsOutputPathDirectory, err => {			if (err) return callback(err);			writeFile();		});	}	readRecords(callback) {		if (!this.recordsInputPath) {			this.records = {};			return callback();		}		this.inputFileSystem.stat(this.recordsInputPath, err => {			// It doesn't exist			// We can ignore this.			if (err) return callback();			this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {				if (err) return callback(err);				try {					this.records = parseJson(content.toString("utf-8"));				} catch (e) {					e.message = "Cannot parse records: " + e.message;					return callback(e);				}				return callback();			});		});	}	createChildCompiler(		compilation,		compilerName,		compilerIndex,		outputOptions,		plugins	) {		const childCompiler = new Compiler(this.context);		if (Array.isArray(plugins)) {			for (const plugin of plugins) {				plugin.apply(childCompiler);			}		}		for (const name in this.hooks) {			if (				![					"make",					"compile",					"emit",					"afterEmit",					"invalid",					"done",					"thisCompilation"				].includes(name)			) {				if (childCompiler.hooks[name]) {					childCompiler.hooks[name].taps = this.hooks[name].taps.slice();				}			}		}		childCompiler.name = compilerName;		childCompiler.outputPath = this.outputPath;		childCompiler.inputFileSystem = this.inputFileSystem;		childCompiler.outputFileSystem = null;		childCompiler.resolverFactory = this.resolverFactory;		childCompiler.fileTimestamps = this.fileTimestamps;		childCompiler.contextTimestamps = this.contextTimestamps;		const relativeCompilerName = makePathsRelative(this.context, compilerName);		if (!this.records[relativeCompilerName]) {			this.records[relativeCompilerName] = [];		}		if (this.records[relativeCompilerName][compilerIndex]) {			childCompiler.records = this.records[relativeCompilerName][compilerIndex];		} else {			this.records[relativeCompilerName].push((childCompiler.records = {}));		}		childCompiler.options = Object.create(this.options);		childCompiler.options.output = Object.create(childCompiler.options.output);		for (const name in outputOptions) {			childCompiler.options.output[name] = outputOptions[name];		}		childCompiler.parentCompilation = compilation;		compilation.hooks.childCompiler.call(			childCompiler,			compilerName,			compilerIndex		);		return childCompiler;	}	isChild() {		return !!this.parentCompilation;	}	createCompilation() {		return new Compilation(this);	}	newCompilation(params) {		const compilation = this.createCompilation();		compilation.fileTimestamps = this.fileTimestamps;		compilation.contextTimestamps = this.contextTimestamps;		compilation.name = this.name;		compilation.records = this.records;		compilation.compilationDependencies = params.compilationDependencies;		this.hooks.thisCompilation.call(compilation, params);		this.hooks.compilation.call(compilation, params);		return compilation;	}	createNormalModuleFactory() {		const normalModuleFactory = new NormalModuleFactory(			this.options.context,			this.resolverFactory,			this.options.module || {}		);		this.hooks.normalModuleFactory.call(normalModuleFactory);		return normalModuleFactory;	}	createContextModuleFactory() {		const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);		this.hooks.contextModuleFactory.call(contextModuleFactory);		return contextModuleFactory;	}	newCompilationParams() {		const params = {			normalModuleFactory: this.createNormalModuleFactory(),			contextModuleFactory: this.createContextModuleFactory(),			compilationDependencies: new Set()		};		return params;	}	compile(callback) {		const params = this.newCompilationParams();		this.hooks.beforeCompile.callAsync(params, err => {			if (err) return callback(err);			this.hooks.compile.call(params);			const compilation = this.newCompilation(params);			this.hooks.make.callAsync(compilation, err => {				if (err) return callback(err);				compilation.finish(err => {					if (err) return callback(err);					compilation.seal(err => {						if (err) return callback(err);						this.hooks.afterCompile.callAsync(compilation, err => {							if (err) return callback(err);							return callback(null, compilation);						});					});				});			});		});	}}module.exports = Compiler;class SizeOnlySource extends Source {	constructor(size) {		super();		this._size = size;	}	_error() {		return new Error(			"Content and Map of this Source is no longer available (only size() is supported)"		);	}	size() {		return this._size;	}	/**	 * @param {any} options options	 * @returns {string} the source	 */	source(options) {		throw this._error();	}	node() {		throw this._error();	}	listMap() {		throw this._error();	}	map() {		throw this._error();	}	listNode() {		throw this._error();	}	updateHash() {		throw this._error();	}}
 |