| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const { SyncBailHook } = require("tapable");const { RawSource } = require("webpack-sources");const Template = require("./Template");const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");const ConstDependency = require("./dependencies/ConstDependency");const NullFactory = require("./NullFactory");const ParserHelpers = require("./ParserHelpers");module.exports = class HotModuleReplacementPlugin {	constructor(options) {		this.options = options || {};		this.multiStep = this.options.multiStep;		this.fullBuildTimeout = this.options.fullBuildTimeout || 200;		this.requestTimeout = this.options.requestTimeout || 10000;	}	apply(compiler) {		const multiStep = this.multiStep;		const fullBuildTimeout = this.fullBuildTimeout;		const requestTimeout = this.requestTimeout;		const hotUpdateChunkFilename =			compiler.options.output.hotUpdateChunkFilename;		const hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;		compiler.hooks.additionalPass.tapAsync(			"HotModuleReplacementPlugin",			callback => {				if (multiStep) return setTimeout(callback, fullBuildTimeout);				return callback();			}		);		const addParserPlugins = (parser, parserOptions) => {			parser.hooks.expression				.for("__webpack_hash__")				.tap(					"HotModuleReplacementPlugin",					ParserHelpers.toConstantDependencyWithWebpackRequire(						parser,						"__webpack_require__.h()"					)				);			parser.hooks.evaluateTypeof				.for("__webpack_hash__")				.tap(					"HotModuleReplacementPlugin",					ParserHelpers.evaluateToString("string")				);			parser.hooks.evaluateIdentifier.for("module.hot").tap(				{					name: "HotModuleReplacementPlugin",					before: "NodeStuffPlugin"				},				expr => {					return ParserHelpers.evaluateToIdentifier(						"module.hot",						!!parser.state.compilation.hotUpdateChunkTemplate					)(expr);				}			);			// TODO webpack 5: refactor this, no custom hooks			if (!parser.hooks.hotAcceptCallback) {				parser.hooks.hotAcceptCallback = new SyncBailHook([					"expression",					"requests"				]);			}			if (!parser.hooks.hotAcceptWithoutCallback) {				parser.hooks.hotAcceptWithoutCallback = new SyncBailHook([					"expression",					"requests"				]);			}			parser.hooks.call				.for("module.hot.accept")				.tap("HotModuleReplacementPlugin", expr => {					if (!parser.state.compilation.hotUpdateChunkTemplate) {						return false;					}					if (expr.arguments.length >= 1) {						const arg = parser.evaluateExpression(expr.arguments[0]);						let params = [];						let requests = [];						if (arg.isString()) {							params = [arg];						} else if (arg.isArray()) {							params = arg.items.filter(param => param.isString());						}						if (params.length > 0) {							params.forEach((param, idx) => {								const request = param.string;								const dep = new ModuleHotAcceptDependency(request, param.range);								dep.optional = true;								dep.loc = Object.create(expr.loc);								dep.loc.index = idx;								parser.state.module.addDependency(dep);								requests.push(request);							});							if (expr.arguments.length > 1) {								parser.hooks.hotAcceptCallback.call(									expr.arguments[1],									requests								);								parser.walkExpression(expr.arguments[1]); // other args are ignored								return true;							} else {								parser.hooks.hotAcceptWithoutCallback.call(expr, requests);								return true;							}						}					}				});			parser.hooks.call				.for("module.hot.decline")				.tap("HotModuleReplacementPlugin", expr => {					if (!parser.state.compilation.hotUpdateChunkTemplate) {						return false;					}					if (expr.arguments.length === 1) {						const arg = parser.evaluateExpression(expr.arguments[0]);						let params = [];						if (arg.isString()) {							params = [arg];						} else if (arg.isArray()) {							params = arg.items.filter(param => param.isString());						}						params.forEach((param, idx) => {							const dep = new ModuleHotDeclineDependency(								param.string,								param.range							);							dep.optional = true;							dep.loc = Object.create(expr.loc);							dep.loc.index = idx;							parser.state.module.addDependency(dep);						});					}				});			parser.hooks.expression				.for("module.hot")				.tap("HotModuleReplacementPlugin", ParserHelpers.skipTraversal);		};		compiler.hooks.compilation.tap(			"HotModuleReplacementPlugin",			(compilation, { normalModuleFactory }) => {				// This applies the HMR plugin only to the targeted compiler				// It should not affect child compilations				if (compilation.compiler !== compiler) return;				const hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;				if (!hotUpdateChunkTemplate) return;				compilation.dependencyFactories.set(ConstDependency, new NullFactory());				compilation.dependencyTemplates.set(					ConstDependency,					new ConstDependency.Template()				);				compilation.dependencyFactories.set(					ModuleHotAcceptDependency,					normalModuleFactory				);				compilation.dependencyTemplates.set(					ModuleHotAcceptDependency,					new ModuleHotAcceptDependency.Template()				);				compilation.dependencyFactories.set(					ModuleHotDeclineDependency,					normalModuleFactory				);				compilation.dependencyTemplates.set(					ModuleHotDeclineDependency,					new ModuleHotDeclineDependency.Template()				);				compilation.hooks.record.tap(					"HotModuleReplacementPlugin",					(compilation, records) => {						if (records.hash === compilation.hash) return;						records.hash = compilation.hash;						records.moduleHashs = {};						for (const module of compilation.modules) {							const identifier = module.identifier();							records.moduleHashs[identifier] = module.hash;						}						records.chunkHashs = {};						for (const chunk of compilation.chunks) {							records.chunkHashs[chunk.id] = chunk.hash;						}						records.chunkModuleIds = {};						for (const chunk of compilation.chunks) {							records.chunkModuleIds[chunk.id] = Array.from(								chunk.modulesIterable,								m => m.id							);						}					}				);				let initialPass = false;				let recompilation = false;				compilation.hooks.afterHash.tap("HotModuleReplacementPlugin", () => {					let records = compilation.records;					if (!records) {						initialPass = true;						return;					}					if (!records.hash) initialPass = true;					const preHash = records.preHash || "x";					const prepreHash = records.prepreHash || "x";					if (preHash === compilation.hash) {						recompilation = true;						compilation.modifyHash(prepreHash);						return;					}					records.prepreHash = records.hash || "x";					records.preHash = compilation.hash;					compilation.modifyHash(records.prepreHash);				});				compilation.hooks.shouldGenerateChunkAssets.tap(					"HotModuleReplacementPlugin",					() => {						if (multiStep && !recompilation && !initialPass) return false;					}				);				compilation.hooks.needAdditionalPass.tap(					"HotModuleReplacementPlugin",					() => {						if (multiStep && !recompilation && !initialPass) return true;					}				);				compilation.hooks.additionalChunkAssets.tap(					"HotModuleReplacementPlugin",					() => {						const records = compilation.records;						if (records.hash === compilation.hash) return;						if (							!records.moduleHashs ||							!records.chunkHashs ||							!records.chunkModuleIds						)							return;						for (const module of compilation.modules) {							const identifier = module.identifier();							let hash = module.hash;							module.hotUpdate = records.moduleHashs[identifier] !== hash;						}						const hotUpdateMainContent = {							h: compilation.hash,							c: {}						};						for (const key of Object.keys(records.chunkHashs)) {							const chunkId = isNaN(+key) ? key : +key;							const currentChunk = compilation.chunks.find(								chunk => `${chunk.id}` === key							);							if (currentChunk) {								const newModules = currentChunk									.getModules()									.filter(module => module.hotUpdate);								const allModules = new Set();								for (const module of currentChunk.modulesIterable) {									allModules.add(module.id);								}								const removedModules = records.chunkModuleIds[chunkId].filter(									id => !allModules.has(id)								);								if (newModules.length > 0 || removedModules.length > 0) {									const source = hotUpdateChunkTemplate.render(										chunkId,										newModules,										removedModules,										compilation.hash,										compilation.moduleTemplates.javascript,										compilation.dependencyTemplates									);									const {										path: filename,										info: assetInfo									} = compilation.getPathWithInfo(hotUpdateChunkFilename, {										hash: records.hash,										chunk: currentChunk									});									compilation.additionalChunkAssets.push(filename);									compilation.emitAsset(										filename,										source,										Object.assign({ hotModuleReplacement: true }, assetInfo)									);									hotUpdateMainContent.c[chunkId] = true;									currentChunk.files.push(filename);									compilation.hooks.chunkAsset.call(currentChunk, filename);								}							} else {								hotUpdateMainContent.c[chunkId] = false;							}						}						const source = new RawSource(JSON.stringify(hotUpdateMainContent));						const {							path: filename,							info: assetInfo						} = compilation.getPathWithInfo(hotUpdateMainFilename, {							hash: records.hash						});						compilation.emitAsset(							filename,							source,							Object.assign({ hotModuleReplacement: true }, assetInfo)						);					}				);				const mainTemplate = compilation.mainTemplate;				mainTemplate.hooks.hash.tap("HotModuleReplacementPlugin", hash => {					hash.update("HotMainTemplateDecorator");				});				mainTemplate.hooks.moduleRequire.tap(					"HotModuleReplacementPlugin",					(_, chunk, hash, varModuleId) => {						return `hotCreateRequire(${varModuleId})`;					}				);				mainTemplate.hooks.requireExtensions.tap(					"HotModuleReplacementPlugin",					source => {						const buf = [source];						buf.push("");						buf.push("// __webpack_hash__");						buf.push(							mainTemplate.requireFn +								".h = function() { return hotCurrentHash; };"						);						return Template.asString(buf);					}				);				const needChunkLoadingCode = chunk => {					for (const chunkGroup of chunk.groupsIterable) {						if (chunkGroup.chunks.length > 1) return true;						if (chunkGroup.getNumberOfChildren() > 0) return true;					}					return false;				};				mainTemplate.hooks.bootstrap.tap(					"HotModuleReplacementPlugin",					(source, chunk, hash) => {						source = mainTemplate.hooks.hotBootstrap.call(source, chunk, hash);						return Template.asString([							source,							"",							hotInitCode								.replace(/\$require\$/g, mainTemplate.requireFn)								.replace(/\$hash\$/g, JSON.stringify(hash))								.replace(/\$requestTimeout\$/g, requestTimeout)								.replace(									/\/\*foreachInstalledChunks\*\//g,									needChunkLoadingCode(chunk)										? "for(var chunkId in installedChunks)"										: `var chunkId = ${JSON.stringify(chunk.id)};`								)						]);					}				);				mainTemplate.hooks.globalHash.tap(					"HotModuleReplacementPlugin",					() => true				);				mainTemplate.hooks.currentHash.tap(					"HotModuleReplacementPlugin",					(_, length) => {						if (isFinite(length)) {							return `hotCurrentHash.substr(0, ${length})`;						} else {							return "hotCurrentHash";						}					}				);				mainTemplate.hooks.moduleObj.tap(					"HotModuleReplacementPlugin",					(source, chunk, hash, varModuleId) => {						return Template.asString([							`${source},`,							`hot: hotCreateModule(${varModuleId}),`,							"parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),",							"children: []"						]);					}				);				// TODO add HMR support for javascript/esm				normalModuleFactory.hooks.parser					.for("javascript/auto")					.tap("HotModuleReplacementPlugin", addParserPlugins);				normalModuleFactory.hooks.parser					.for("javascript/dynamic")					.tap("HotModuleReplacementPlugin", addParserPlugins);				compilation.hooks.normalModuleLoader.tap(					"HotModuleReplacementPlugin",					context => {						context.hot = true;					}				);			}		);	}};const hotInitCode = Template.getFunctionContent(	require("./HotModuleReplacement.runtime"));
 |