text: /*
  	MIT License http://www.opensource.org/licenses/mit-license.php
  	Author Tobias Koppers @sokra
  */
  "use strict";
  
  const asyncLib = require("neo-async");
  const path = require("path");
  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");
  
  class Compiler extends Tapable {
  	constructor(context) {
  		super();
  		this.hooks = {
  			shouldEmit: new SyncBailHook(["compilation"]),
  			done: new AsyncSeriesHook(["stats"]),
  			additionalPass: new AsyncSeriesHook([]),
  			beforeRun: new AsyncSeriesHook(["compilation"]),
  			run: new AsyncSeriesHook(["compilation"]),
  			emit: new AsyncSeriesHook(["compilation"]),
  			afterEmit: new AsyncSeriesHook(["compilation"]),
  			thisCompilation: new SyncHook(["compilation", "params"]),
  			compilation: new SyncHook(["compilation", "params"]),
  			normalModuleFactory: new SyncHook(["normalModuleFactory"]),
  			contextModuleFactory: new SyncHook(["contextModulefactory"]),
  			beforeCompile: new AsyncSeriesHook(["params"]),
  			compile: new SyncHook(["params"]),
  			make: new AsyncParallelHook(["compilation"]),
  			afterCompile: new AsyncSeriesHook(["compilation"]),
  			watchRun: new AsyncSeriesHook(["compiler"]),
  			failed: new SyncHook(["error"]),
  			invalid: new SyncHook(["filename", "changeTime"]),
  			watchClose: new SyncHook([]),
  
  			// TODO the following hooks are weirdly located here
  			// TODO move them for webpack 5
  			environment: new SyncHook([]),
  			afterEnvironment: new SyncHook([]),
  			afterPlugins: new SyncHook(["compiler"]),
  			afterResolvers: new SyncHook(["compiler"]),
  			entryOption: new SyncBailHook(["context", "entry"])
  		};
  		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;
  			}
  		});
  
  		this.name = undefined;
  		this.parentCompilation = undefined;
  		this.outputPath = "";
  		this.outputFileSystem = null;
  		this.inputFileSystem = null;
  
  		this.recordsInputPath = null;
  		this.recordsOutputPath = null;
  		this.records = {};
  
  		this.fileTimestamps = new Map();
  		this.contextTimestamps = new Map();
  
  		this.resolverFactory = new ResolverFactory();
  
  		// 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.')
  			}
  		};
  
  		this.options = {};
  
  		this.context = context;
  
  		this.requestShortener = new RequestShortener(context);
  
  		this.running = false;
  	}
  
  	watch(watchOptions, handler) {
  		if (this.running) return handler(new ConcurrentCompilationError());
  
  		this.running = true;
  		this.fileTimestamps = new Map();
  		this.contextTimestamps = new Map();
  		return new Watching(this, watchOptions, handler);
  	}
  
  	run(callback) {
  		if (this.running) return callback(new ConcurrentCompilationError());
  
  		const finalCallback = (err, stats) => {
  			this.running = false;
  
  			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 of Object.keys(compilation.assets)) {
  				this.parentCompilation.assets[name] = compilation.assets[name];
  			}
  
  			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.forEach(
  				compilation.assets,
  				(source, file, 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
  						);
  						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, 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);
  		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 = JSON.parse(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();
  
  				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;
  