[79a0317] | 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 { SyncBailHook } = require("tapable");
|
---|
| 9 | const { RawSource } = require("webpack-sources");
|
---|
| 10 | const ChunkGraph = require("./ChunkGraph");
|
---|
| 11 | const Compilation = require("./Compilation");
|
---|
| 12 | const HotUpdateChunk = require("./HotUpdateChunk");
|
---|
| 13 | const NormalModule = require("./NormalModule");
|
---|
| 14 | const RuntimeGlobals = require("./RuntimeGlobals");
|
---|
| 15 | const WebpackError = require("./WebpackError");
|
---|
| 16 | const ConstDependency = require("./dependencies/ConstDependency");
|
---|
| 17 | const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
|
---|
| 18 | const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
|
---|
| 19 | const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
|
---|
| 20 | const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
|
---|
| 21 | const HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule");
|
---|
| 22 | const JavascriptParser = require("./javascript/JavascriptParser");
|
---|
| 23 | const {
|
---|
| 24 | evaluateToIdentifier
|
---|
| 25 | } = require("./javascript/JavascriptParserHelpers");
|
---|
| 26 | const { find, isSubset } = require("./util/SetHelpers");
|
---|
| 27 | const TupleSet = require("./util/TupleSet");
|
---|
| 28 | const { compareModulesById } = require("./util/comparators");
|
---|
| 29 | const {
|
---|
| 30 | getRuntimeKey,
|
---|
| 31 | keyToRuntime,
|
---|
| 32 | forEachRuntime,
|
---|
| 33 | mergeRuntimeOwned,
|
---|
| 34 | subtractRuntime,
|
---|
| 35 | intersectRuntime
|
---|
| 36 | } = require("./util/runtime");
|
---|
| 37 |
|
---|
| 38 | const {
|
---|
| 39 | JAVASCRIPT_MODULE_TYPE_AUTO,
|
---|
| 40 | JAVASCRIPT_MODULE_TYPE_DYNAMIC,
|
---|
| 41 | JAVASCRIPT_MODULE_TYPE_ESM,
|
---|
| 42 | WEBPACK_MODULE_TYPE_RUNTIME
|
---|
| 43 | } = require("./ModuleTypeConstants");
|
---|
| 44 |
|
---|
| 45 | /** @typedef {import("estree").CallExpression} CallExpression */
|
---|
| 46 | /** @typedef {import("estree").Expression} Expression */
|
---|
| 47 | /** @typedef {import("estree").SpreadElement} SpreadElement */
|
---|
| 48 | /** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputNormalized */
|
---|
| 49 | /** @typedef {import("./Chunk")} Chunk */
|
---|
| 50 | /** @typedef {import("./Chunk").ChunkId} ChunkId */
|
---|
| 51 | /** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
|
---|
| 52 | /** @typedef {import("./Compilation").AssetInfo} AssetInfo */
|
---|
| 53 | /** @typedef {import("./Compiler")} Compiler */
|
---|
| 54 | /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
|
---|
| 55 | /** @typedef {import("./Module")} Module */
|
---|
| 56 | /** @typedef {import("./Module").BuildInfo} BuildInfo */
|
---|
| 57 | /** @typedef {import("./RuntimeModule")} RuntimeModule */
|
---|
| 58 | /** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
|
---|
| 59 | /** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */
|
---|
| 60 | /** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
|
---|
| 61 |
|
---|
| 62 | /**
|
---|
| 63 | * @typedef {object} HMRJavascriptParserHooks
|
---|
| 64 | * @property {SyncBailHook<[Expression | SpreadElement, string[]], void>} hotAcceptCallback
|
---|
| 65 | * @property {SyncBailHook<[CallExpression, string[]], void>} hotAcceptWithoutCallback
|
---|
| 66 | */
|
---|
| 67 |
|
---|
| 68 | /** @typedef {{ updatedChunkIds: Set<ChunkId>, removedChunkIds: Set<ChunkId>, removedModules: Set<Module>, filename: string, assetInfo: AssetInfo }} HotUpdateMainContentByRuntimeItem */
|
---|
| 69 | /** @typedef {Map<string, HotUpdateMainContentByRuntimeItem>} HotUpdateMainContentByRuntime */
|
---|
| 70 |
|
---|
| 71 | /** @type {WeakMap<JavascriptParser, HMRJavascriptParserHooks>} */
|
---|
| 72 | const parserHooksMap = new WeakMap();
|
---|
| 73 |
|
---|
| 74 | const PLUGIN_NAME = "HotModuleReplacementPlugin";
|
---|
| 75 |
|
---|
| 76 | class HotModuleReplacementPlugin {
|
---|
| 77 | /**
|
---|
| 78 | * @param {JavascriptParser} parser the parser
|
---|
| 79 | * @returns {HMRJavascriptParserHooks} the attached hooks
|
---|
| 80 | */
|
---|
| 81 | static getParserHooks(parser) {
|
---|
| 82 | if (!(parser instanceof JavascriptParser)) {
|
---|
| 83 | throw new TypeError(
|
---|
| 84 | "The 'parser' argument must be an instance of JavascriptParser"
|
---|
| 85 | );
|
---|
| 86 | }
|
---|
| 87 | let hooks = parserHooksMap.get(parser);
|
---|
| 88 | if (hooks === undefined) {
|
---|
| 89 | hooks = {
|
---|
| 90 | hotAcceptCallback: new SyncBailHook(["expression", "requests"]),
|
---|
| 91 | hotAcceptWithoutCallback: new SyncBailHook(["expression", "requests"])
|
---|
| 92 | };
|
---|
| 93 | parserHooksMap.set(parser, hooks);
|
---|
| 94 | }
|
---|
| 95 | return hooks;
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | /**
|
---|
| 99 | * @param {object=} options options
|
---|
| 100 | */
|
---|
| 101 | constructor(options) {
|
---|
| 102 | this.options = options || {};
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | /**
|
---|
| 106 | * Apply the plugin
|
---|
| 107 | * @param {Compiler} compiler the compiler instance
|
---|
| 108 | * @returns {void}
|
---|
| 109 | */
|
---|
| 110 | apply(compiler) {
|
---|
| 111 | const { _backCompat: backCompat } = compiler;
|
---|
| 112 | if (compiler.options.output.strictModuleErrorHandling === undefined)
|
---|
| 113 | compiler.options.output.strictModuleErrorHandling = true;
|
---|
| 114 | const runtimeRequirements = [RuntimeGlobals.module];
|
---|
| 115 |
|
---|
| 116 | /**
|
---|
| 117 | * @param {JavascriptParser} parser the parser
|
---|
| 118 | * @param {typeof ModuleHotAcceptDependency} ParamDependency dependency
|
---|
| 119 | * @returns {(expr: CallExpression) => boolean | undefined} callback
|
---|
| 120 | */
|
---|
| 121 | const createAcceptHandler = (parser, ParamDependency) => {
|
---|
| 122 | const { hotAcceptCallback, hotAcceptWithoutCallback } =
|
---|
| 123 | HotModuleReplacementPlugin.getParserHooks(parser);
|
---|
| 124 |
|
---|
| 125 | return expr => {
|
---|
| 126 | const module = parser.state.module;
|
---|
| 127 | const dep = new ConstDependency(
|
---|
| 128 | `${module.moduleArgument}.hot.accept`,
|
---|
| 129 | /** @type {Range} */ (expr.callee.range),
|
---|
| 130 | runtimeRequirements
|
---|
| 131 | );
|
---|
| 132 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
---|
| 133 | module.addPresentationalDependency(dep);
|
---|
| 134 | /** @type {BuildInfo} */
|
---|
| 135 | (module.buildInfo).moduleConcatenationBailout =
|
---|
| 136 | "Hot Module Replacement";
|
---|
| 137 |
|
---|
| 138 | if (expr.arguments.length >= 1) {
|
---|
| 139 | const arg = parser.evaluateExpression(expr.arguments[0]);
|
---|
| 140 | /** @type {BasicEvaluatedExpression[]} */
|
---|
| 141 | let params = [];
|
---|
| 142 | if (arg.isString()) {
|
---|
| 143 | params = [arg];
|
---|
| 144 | } else if (arg.isArray()) {
|
---|
| 145 | params =
|
---|
| 146 | /** @type {BasicEvaluatedExpression[]} */
|
---|
| 147 | (arg.items).filter(param => param.isString());
|
---|
| 148 | }
|
---|
| 149 | /** @type {string[]} */
|
---|
| 150 | const requests = [];
|
---|
| 151 | if (params.length > 0) {
|
---|
| 152 | for (const [idx, param] of params.entries()) {
|
---|
| 153 | const request = /** @type {string} */ (param.string);
|
---|
| 154 | const dep = new ParamDependency(
|
---|
| 155 | request,
|
---|
| 156 | /** @type {Range} */ (param.range)
|
---|
| 157 | );
|
---|
| 158 | dep.optional = true;
|
---|
| 159 | dep.loc = Object.create(
|
---|
| 160 | /** @type {DependencyLocation} */ (expr.loc)
|
---|
| 161 | );
|
---|
| 162 | dep.loc.index = idx;
|
---|
| 163 | module.addDependency(dep);
|
---|
| 164 | requests.push(request);
|
---|
| 165 | }
|
---|
| 166 | if (expr.arguments.length > 1) {
|
---|
| 167 | hotAcceptCallback.call(expr.arguments[1], requests);
|
---|
| 168 | for (let i = 1; i < expr.arguments.length; i++) {
|
---|
| 169 | parser.walkExpression(expr.arguments[i]);
|
---|
| 170 | }
|
---|
| 171 | return true;
|
---|
| 172 | }
|
---|
| 173 | hotAcceptWithoutCallback.call(expr, requests);
|
---|
| 174 | return true;
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 | parser.walkExpressions(expr.arguments);
|
---|
| 178 | return true;
|
---|
| 179 | };
|
---|
| 180 | };
|
---|
| 181 |
|
---|
| 182 | /**
|
---|
| 183 | * @param {JavascriptParser} parser the parser
|
---|
| 184 | * @param {typeof ModuleHotDeclineDependency} ParamDependency dependency
|
---|
| 185 | * @returns {(expr: CallExpression) => boolean | undefined} callback
|
---|
| 186 | */
|
---|
| 187 | const createDeclineHandler = (parser, ParamDependency) => expr => {
|
---|
| 188 | const module = parser.state.module;
|
---|
| 189 | const dep = new ConstDependency(
|
---|
| 190 | `${module.moduleArgument}.hot.decline`,
|
---|
| 191 | /** @type {Range} */ (expr.callee.range),
|
---|
| 192 | runtimeRequirements
|
---|
| 193 | );
|
---|
| 194 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
---|
| 195 | module.addPresentationalDependency(dep);
|
---|
| 196 | /** @type {BuildInfo} */
|
---|
| 197 | (module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
|
---|
| 198 | if (expr.arguments.length === 1) {
|
---|
| 199 | const arg = parser.evaluateExpression(expr.arguments[0]);
|
---|
| 200 | /** @type {BasicEvaluatedExpression[]} */
|
---|
| 201 | let params = [];
|
---|
| 202 | if (arg.isString()) {
|
---|
| 203 | params = [arg];
|
---|
| 204 | } else if (arg.isArray()) {
|
---|
| 205 | params =
|
---|
| 206 | /** @type {BasicEvaluatedExpression[]} */
|
---|
| 207 | (arg.items).filter(param => param.isString());
|
---|
| 208 | }
|
---|
| 209 | for (const [idx, param] of params.entries()) {
|
---|
| 210 | const dep = new ParamDependency(
|
---|
| 211 | /** @type {string} */ (param.string),
|
---|
| 212 | /** @type {Range} */ (param.range)
|
---|
| 213 | );
|
---|
| 214 | dep.optional = true;
|
---|
| 215 | dep.loc = Object.create(/** @type {DependencyLocation} */ (expr.loc));
|
---|
| 216 | dep.loc.index = idx;
|
---|
| 217 | module.addDependency(dep);
|
---|
| 218 | }
|
---|
| 219 | }
|
---|
| 220 | return true;
|
---|
| 221 | };
|
---|
| 222 |
|
---|
| 223 | /**
|
---|
| 224 | * @param {JavascriptParser} parser the parser
|
---|
| 225 | * @returns {(expr: Expression) => boolean | undefined} callback
|
---|
| 226 | */
|
---|
| 227 | const createHMRExpressionHandler = parser => expr => {
|
---|
| 228 | const module = parser.state.module;
|
---|
| 229 | const dep = new ConstDependency(
|
---|
| 230 | `${module.moduleArgument}.hot`,
|
---|
| 231 | /** @type {Range} */ (expr.range),
|
---|
| 232 | runtimeRequirements
|
---|
| 233 | );
|
---|
| 234 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
---|
| 235 | module.addPresentationalDependency(dep);
|
---|
| 236 | /** @type {BuildInfo} */
|
---|
| 237 | (module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
|
---|
| 238 | return true;
|
---|
| 239 | };
|
---|
| 240 |
|
---|
| 241 | /**
|
---|
| 242 | * @param {JavascriptParser} parser the parser
|
---|
| 243 | * @returns {void}
|
---|
| 244 | */
|
---|
| 245 | const applyModuleHot = parser => {
|
---|
| 246 | parser.hooks.evaluateIdentifier.for("module.hot").tap(
|
---|
| 247 | {
|
---|
| 248 | name: PLUGIN_NAME,
|
---|
| 249 | before: "NodeStuffPlugin"
|
---|
| 250 | },
|
---|
| 251 | expr =>
|
---|
| 252 | evaluateToIdentifier(
|
---|
| 253 | "module.hot",
|
---|
| 254 | "module",
|
---|
| 255 | () => ["hot"],
|
---|
| 256 | true
|
---|
| 257 | )(expr)
|
---|
| 258 | );
|
---|
| 259 | parser.hooks.call
|
---|
| 260 | .for("module.hot.accept")
|
---|
| 261 | .tap(
|
---|
| 262 | PLUGIN_NAME,
|
---|
| 263 | createAcceptHandler(parser, ModuleHotAcceptDependency)
|
---|
| 264 | );
|
---|
| 265 | parser.hooks.call
|
---|
| 266 | .for("module.hot.decline")
|
---|
| 267 | .tap(
|
---|
| 268 | PLUGIN_NAME,
|
---|
| 269 | createDeclineHandler(parser, ModuleHotDeclineDependency)
|
---|
| 270 | );
|
---|
| 271 | parser.hooks.expression
|
---|
| 272 | .for("module.hot")
|
---|
| 273 | .tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
|
---|
| 274 | };
|
---|
| 275 |
|
---|
| 276 | /**
|
---|
| 277 | * @param {JavascriptParser} parser the parser
|
---|
| 278 | * @returns {void}
|
---|
| 279 | */
|
---|
| 280 | const applyImportMetaHot = parser => {
|
---|
| 281 | parser.hooks.evaluateIdentifier
|
---|
| 282 | .for("import.meta.webpackHot")
|
---|
| 283 | .tap(PLUGIN_NAME, expr =>
|
---|
| 284 | evaluateToIdentifier(
|
---|
| 285 | "import.meta.webpackHot",
|
---|
| 286 | "import.meta",
|
---|
| 287 | () => ["webpackHot"],
|
---|
| 288 | true
|
---|
| 289 | )(expr)
|
---|
| 290 | );
|
---|
| 291 | parser.hooks.call
|
---|
| 292 | .for("import.meta.webpackHot.accept")
|
---|
| 293 | .tap(
|
---|
| 294 | PLUGIN_NAME,
|
---|
| 295 | createAcceptHandler(parser, ImportMetaHotAcceptDependency)
|
---|
| 296 | );
|
---|
| 297 | parser.hooks.call
|
---|
| 298 | .for("import.meta.webpackHot.decline")
|
---|
| 299 | .tap(
|
---|
| 300 | PLUGIN_NAME,
|
---|
| 301 | createDeclineHandler(parser, ImportMetaHotDeclineDependency)
|
---|
| 302 | );
|
---|
| 303 | parser.hooks.expression
|
---|
| 304 | .for("import.meta.webpackHot")
|
---|
| 305 | .tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
|
---|
| 306 | };
|
---|
| 307 |
|
---|
| 308 | compiler.hooks.compilation.tap(
|
---|
| 309 | PLUGIN_NAME,
|
---|
| 310 | (compilation, { normalModuleFactory }) => {
|
---|
| 311 | // This applies the HMR plugin only to the targeted compiler
|
---|
| 312 | // It should not affect child compilations
|
---|
| 313 | if (compilation.compiler !== compiler) return;
|
---|
| 314 |
|
---|
| 315 | // #region module.hot.* API
|
---|
| 316 | compilation.dependencyFactories.set(
|
---|
| 317 | ModuleHotAcceptDependency,
|
---|
| 318 | normalModuleFactory
|
---|
| 319 | );
|
---|
| 320 | compilation.dependencyTemplates.set(
|
---|
| 321 | ModuleHotAcceptDependency,
|
---|
| 322 | new ModuleHotAcceptDependency.Template()
|
---|
| 323 | );
|
---|
| 324 | compilation.dependencyFactories.set(
|
---|
| 325 | ModuleHotDeclineDependency,
|
---|
| 326 | normalModuleFactory
|
---|
| 327 | );
|
---|
| 328 | compilation.dependencyTemplates.set(
|
---|
| 329 | ModuleHotDeclineDependency,
|
---|
| 330 | new ModuleHotDeclineDependency.Template()
|
---|
| 331 | );
|
---|
| 332 | // #endregion
|
---|
| 333 |
|
---|
| 334 | // #region import.meta.webpackHot.* API
|
---|
| 335 | compilation.dependencyFactories.set(
|
---|
| 336 | ImportMetaHotAcceptDependency,
|
---|
| 337 | normalModuleFactory
|
---|
| 338 | );
|
---|
| 339 | compilation.dependencyTemplates.set(
|
---|
| 340 | ImportMetaHotAcceptDependency,
|
---|
| 341 | new ImportMetaHotAcceptDependency.Template()
|
---|
| 342 | );
|
---|
| 343 | compilation.dependencyFactories.set(
|
---|
| 344 | ImportMetaHotDeclineDependency,
|
---|
| 345 | normalModuleFactory
|
---|
| 346 | );
|
---|
| 347 | compilation.dependencyTemplates.set(
|
---|
| 348 | ImportMetaHotDeclineDependency,
|
---|
| 349 | new ImportMetaHotDeclineDependency.Template()
|
---|
| 350 | );
|
---|
| 351 | // #endregion
|
---|
| 352 |
|
---|
| 353 | let hotIndex = 0;
|
---|
| 354 | /** @type {Record<string, string>} */
|
---|
| 355 | const fullHashChunkModuleHashes = {};
|
---|
| 356 | /** @type {Record<string, string>} */
|
---|
| 357 | const chunkModuleHashes = {};
|
---|
| 358 |
|
---|
| 359 | compilation.hooks.record.tap(PLUGIN_NAME, (compilation, records) => {
|
---|
| 360 | if (records.hash === compilation.hash) return;
|
---|
| 361 | const chunkGraph = compilation.chunkGraph;
|
---|
| 362 | records.hash = compilation.hash;
|
---|
| 363 | records.hotIndex = hotIndex;
|
---|
| 364 | records.fullHashChunkModuleHashes = fullHashChunkModuleHashes;
|
---|
| 365 | records.chunkModuleHashes = chunkModuleHashes;
|
---|
| 366 | records.chunkHashes = {};
|
---|
| 367 | records.chunkRuntime = {};
|
---|
| 368 | for (const chunk of compilation.chunks) {
|
---|
| 369 | const chunkId = /** @type {ChunkId} */ (chunk.id);
|
---|
| 370 | records.chunkHashes[chunkId] = chunk.hash;
|
---|
| 371 | records.chunkRuntime[chunkId] = getRuntimeKey(chunk.runtime);
|
---|
| 372 | }
|
---|
| 373 | records.chunkModuleIds = {};
|
---|
| 374 | for (const chunk of compilation.chunks) {
|
---|
| 375 | records.chunkModuleIds[/** @type {ChunkId} */ (chunk.id)] =
|
---|
| 376 | Array.from(
|
---|
| 377 | chunkGraph.getOrderedChunkModulesIterable(
|
---|
| 378 | chunk,
|
---|
| 379 | compareModulesById(chunkGraph)
|
---|
| 380 | ),
|
---|
| 381 | m => chunkGraph.getModuleId(m)
|
---|
| 382 | );
|
---|
| 383 | }
|
---|
| 384 | });
|
---|
| 385 | /** @type {TupleSet<[Module, Chunk]>} */
|
---|
| 386 | const updatedModules = new TupleSet();
|
---|
| 387 | /** @type {TupleSet<[Module, Chunk]>} */
|
---|
| 388 | const fullHashModules = new TupleSet();
|
---|
| 389 | /** @type {TupleSet<[Module, RuntimeSpec]>} */
|
---|
| 390 | const nonCodeGeneratedModules = new TupleSet();
|
---|
| 391 | compilation.hooks.fullHash.tap(PLUGIN_NAME, hash => {
|
---|
| 392 | const chunkGraph = compilation.chunkGraph;
|
---|
| 393 | const records = compilation.records;
|
---|
| 394 | for (const chunk of compilation.chunks) {
|
---|
| 395 | /**
|
---|
| 396 | * @param {Module} module module
|
---|
| 397 | * @returns {string} module hash
|
---|
| 398 | */
|
---|
| 399 | const getModuleHash = module => {
|
---|
| 400 | if (
|
---|
| 401 | compilation.codeGenerationResults.has(module, chunk.runtime)
|
---|
| 402 | ) {
|
---|
| 403 | return compilation.codeGenerationResults.getHash(
|
---|
| 404 | module,
|
---|
| 405 | chunk.runtime
|
---|
| 406 | );
|
---|
| 407 | }
|
---|
| 408 | nonCodeGeneratedModules.add(module, chunk.runtime);
|
---|
| 409 | return chunkGraph.getModuleHash(module, chunk.runtime);
|
---|
| 410 | };
|
---|
| 411 | const fullHashModulesInThisChunk =
|
---|
| 412 | chunkGraph.getChunkFullHashModulesSet(chunk);
|
---|
| 413 | if (fullHashModulesInThisChunk !== undefined) {
|
---|
| 414 | for (const module of fullHashModulesInThisChunk) {
|
---|
| 415 | fullHashModules.add(module, chunk);
|
---|
| 416 | }
|
---|
| 417 | }
|
---|
| 418 | const modules = chunkGraph.getChunkModulesIterable(chunk);
|
---|
| 419 | if (modules !== undefined) {
|
---|
| 420 | if (records.chunkModuleHashes) {
|
---|
| 421 | if (fullHashModulesInThisChunk !== undefined) {
|
---|
| 422 | for (const module of modules) {
|
---|
| 423 | const key = `${chunk.id}|${module.identifier()}`;
|
---|
| 424 | const hash = getModuleHash(module);
|
---|
| 425 | if (
|
---|
| 426 | fullHashModulesInThisChunk.has(
|
---|
| 427 | /** @type {RuntimeModule} */ (module)
|
---|
| 428 | )
|
---|
| 429 | ) {
|
---|
| 430 | if (records.fullHashChunkModuleHashes[key] !== hash) {
|
---|
| 431 | updatedModules.add(module, chunk);
|
---|
| 432 | }
|
---|
| 433 | fullHashChunkModuleHashes[key] = hash;
|
---|
| 434 | } else {
|
---|
| 435 | if (records.chunkModuleHashes[key] !== hash) {
|
---|
| 436 | updatedModules.add(module, chunk);
|
---|
| 437 | }
|
---|
| 438 | chunkModuleHashes[key] = hash;
|
---|
| 439 | }
|
---|
| 440 | }
|
---|
| 441 | } else {
|
---|
| 442 | for (const module of modules) {
|
---|
| 443 | const key = `${chunk.id}|${module.identifier()}`;
|
---|
| 444 | const hash = getModuleHash(module);
|
---|
| 445 | if (records.chunkModuleHashes[key] !== hash) {
|
---|
| 446 | updatedModules.add(module, chunk);
|
---|
| 447 | }
|
---|
| 448 | chunkModuleHashes[key] = hash;
|
---|
| 449 | }
|
---|
| 450 | }
|
---|
| 451 | } else if (fullHashModulesInThisChunk !== undefined) {
|
---|
| 452 | for (const module of modules) {
|
---|
| 453 | const key = `${chunk.id}|${module.identifier()}`;
|
---|
| 454 | const hash = getModuleHash(module);
|
---|
| 455 | if (
|
---|
| 456 | fullHashModulesInThisChunk.has(
|
---|
| 457 | /** @type {RuntimeModule} */ (module)
|
---|
| 458 | )
|
---|
| 459 | ) {
|
---|
| 460 | fullHashChunkModuleHashes[key] = hash;
|
---|
| 461 | } else {
|
---|
| 462 | chunkModuleHashes[key] = hash;
|
---|
| 463 | }
|
---|
| 464 | }
|
---|
| 465 | } else {
|
---|
| 466 | for (const module of modules) {
|
---|
| 467 | const key = `${chunk.id}|${module.identifier()}`;
|
---|
| 468 | const hash = getModuleHash(module);
|
---|
| 469 | chunkModuleHashes[key] = hash;
|
---|
| 470 | }
|
---|
| 471 | }
|
---|
| 472 | }
|
---|
| 473 | }
|
---|
| 474 |
|
---|
| 475 | hotIndex = records.hotIndex || 0;
|
---|
| 476 | if (updatedModules.size > 0) hotIndex++;
|
---|
| 477 |
|
---|
| 478 | hash.update(`${hotIndex}`);
|
---|
| 479 | });
|
---|
| 480 | compilation.hooks.processAssets.tap(
|
---|
| 481 | {
|
---|
| 482 | name: PLUGIN_NAME,
|
---|
| 483 | stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
|
---|
| 484 | },
|
---|
| 485 | () => {
|
---|
| 486 | const chunkGraph = compilation.chunkGraph;
|
---|
| 487 | const records = compilation.records;
|
---|
| 488 | if (records.hash === compilation.hash) return;
|
---|
| 489 | if (
|
---|
| 490 | !records.chunkModuleHashes ||
|
---|
| 491 | !records.chunkHashes ||
|
---|
| 492 | !records.chunkModuleIds
|
---|
| 493 | ) {
|
---|
| 494 | return;
|
---|
| 495 | }
|
---|
| 496 | for (const [module, chunk] of fullHashModules) {
|
---|
| 497 | const key = `${chunk.id}|${module.identifier()}`;
|
---|
| 498 | const hash = nonCodeGeneratedModules.has(module, chunk.runtime)
|
---|
| 499 | ? chunkGraph.getModuleHash(module, chunk.runtime)
|
---|
| 500 | : compilation.codeGenerationResults.getHash(
|
---|
| 501 | module,
|
---|
| 502 | chunk.runtime
|
---|
| 503 | );
|
---|
| 504 | if (records.chunkModuleHashes[key] !== hash) {
|
---|
| 505 | updatedModules.add(module, chunk);
|
---|
| 506 | }
|
---|
| 507 | chunkModuleHashes[key] = hash;
|
---|
| 508 | }
|
---|
| 509 |
|
---|
| 510 | /** @type {HotUpdateMainContentByRuntime} */
|
---|
| 511 | const hotUpdateMainContentByRuntime = new Map();
|
---|
| 512 | let allOldRuntime;
|
---|
| 513 | for (const key of Object.keys(records.chunkRuntime)) {
|
---|
| 514 | const runtime = keyToRuntime(records.chunkRuntime[key]);
|
---|
| 515 | allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime);
|
---|
| 516 | }
|
---|
| 517 | forEachRuntime(allOldRuntime, runtime => {
|
---|
| 518 | const { path: filename, info: assetInfo } =
|
---|
| 519 | compilation.getPathWithInfo(
|
---|
| 520 | /** @type {NonNullable<OutputNormalized["hotUpdateMainFilename"]>} */
|
---|
| 521 | (compilation.outputOptions.hotUpdateMainFilename),
|
---|
| 522 | {
|
---|
| 523 | hash: records.hash,
|
---|
| 524 | runtime
|
---|
| 525 | }
|
---|
| 526 | );
|
---|
| 527 | hotUpdateMainContentByRuntime.set(
|
---|
| 528 | /** @type {string} */ (runtime),
|
---|
| 529 | {
|
---|
| 530 | updatedChunkIds: new Set(),
|
---|
| 531 | removedChunkIds: new Set(),
|
---|
| 532 | removedModules: new Set(),
|
---|
| 533 | filename,
|
---|
| 534 | assetInfo
|
---|
| 535 | }
|
---|
| 536 | );
|
---|
| 537 | });
|
---|
| 538 | if (hotUpdateMainContentByRuntime.size === 0) return;
|
---|
| 539 |
|
---|
| 540 | // Create a list of all active modules to verify which modules are removed completely
|
---|
| 541 | /** @type {Map<number|string, Module>} */
|
---|
| 542 | const allModules = new Map();
|
---|
| 543 | for (const module of compilation.modules) {
|
---|
| 544 | const id =
|
---|
| 545 | /** @type {ModuleId} */
|
---|
| 546 | (chunkGraph.getModuleId(module));
|
---|
| 547 | allModules.set(id, module);
|
---|
| 548 | }
|
---|
| 549 |
|
---|
| 550 | // List of completely removed modules
|
---|
| 551 | /** @type {Set<string | number>} */
|
---|
| 552 | const completelyRemovedModules = new Set();
|
---|
| 553 |
|
---|
| 554 | for (const key of Object.keys(records.chunkHashes)) {
|
---|
| 555 | const oldRuntime = keyToRuntime(records.chunkRuntime[key]);
|
---|
| 556 | /** @type {Module[]} */
|
---|
| 557 | const remainingModules = [];
|
---|
| 558 | // Check which modules are removed
|
---|
| 559 | for (const id of records.chunkModuleIds[key]) {
|
---|
| 560 | const module = allModules.get(id);
|
---|
| 561 | if (module === undefined) {
|
---|
| 562 | completelyRemovedModules.add(id);
|
---|
| 563 | } else {
|
---|
| 564 | remainingModules.push(module);
|
---|
| 565 | }
|
---|
| 566 | }
|
---|
| 567 |
|
---|
| 568 | /** @type {ChunkId | null} */
|
---|
| 569 | let chunkId;
|
---|
| 570 | let newModules;
|
---|
| 571 | let newRuntimeModules;
|
---|
| 572 | let newFullHashModules;
|
---|
| 573 | let newDependentHashModules;
|
---|
| 574 | let newRuntime;
|
---|
| 575 | let removedFromRuntime;
|
---|
| 576 | const currentChunk = find(
|
---|
| 577 | compilation.chunks,
|
---|
| 578 | chunk => `${chunk.id}` === key
|
---|
| 579 | );
|
---|
| 580 | if (currentChunk) {
|
---|
| 581 | chunkId = currentChunk.id;
|
---|
| 582 | newRuntime = intersectRuntime(
|
---|
| 583 | currentChunk.runtime,
|
---|
| 584 | allOldRuntime
|
---|
| 585 | );
|
---|
| 586 | if (newRuntime === undefined) continue;
|
---|
| 587 | newModules = chunkGraph
|
---|
| 588 | .getChunkModules(currentChunk)
|
---|
| 589 | .filter(module => updatedModules.has(module, currentChunk));
|
---|
| 590 | newRuntimeModules = Array.from(
|
---|
| 591 | chunkGraph.getChunkRuntimeModulesIterable(currentChunk)
|
---|
| 592 | ).filter(module => updatedModules.has(module, currentChunk));
|
---|
| 593 | const fullHashModules =
|
---|
| 594 | chunkGraph.getChunkFullHashModulesIterable(currentChunk);
|
---|
| 595 | newFullHashModules =
|
---|
| 596 | fullHashModules &&
|
---|
| 597 | Array.from(fullHashModules).filter(module =>
|
---|
| 598 | updatedModules.has(module, currentChunk)
|
---|
| 599 | );
|
---|
| 600 | const dependentHashModules =
|
---|
| 601 | chunkGraph.getChunkDependentHashModulesIterable(currentChunk);
|
---|
| 602 | newDependentHashModules =
|
---|
| 603 | dependentHashModules &&
|
---|
| 604 | Array.from(dependentHashModules).filter(module =>
|
---|
| 605 | updatedModules.has(module, currentChunk)
|
---|
| 606 | );
|
---|
| 607 | removedFromRuntime = subtractRuntime(oldRuntime, newRuntime);
|
---|
| 608 | } else {
|
---|
| 609 | // chunk has completely removed
|
---|
| 610 | chunkId = `${Number(key)}` === key ? Number(key) : key;
|
---|
| 611 | removedFromRuntime = oldRuntime;
|
---|
| 612 | newRuntime = oldRuntime;
|
---|
| 613 | }
|
---|
| 614 | if (removedFromRuntime) {
|
---|
| 615 | // chunk was removed from some runtimes
|
---|
| 616 | forEachRuntime(removedFromRuntime, runtime => {
|
---|
| 617 | const item =
|
---|
| 618 | /** @type {HotUpdateMainContentByRuntimeItem} */
|
---|
| 619 | (
|
---|
| 620 | hotUpdateMainContentByRuntime.get(
|
---|
| 621 | /** @type {string} */ (runtime)
|
---|
| 622 | )
|
---|
| 623 | );
|
---|
| 624 | item.removedChunkIds.add(/** @type {ChunkId} */ (chunkId));
|
---|
| 625 | });
|
---|
| 626 | // dispose modules from the chunk in these runtimes
|
---|
| 627 | // where they are no longer in this runtime
|
---|
| 628 | for (const module of remainingModules) {
|
---|
| 629 | const moduleKey = `${key}|${module.identifier()}`;
|
---|
| 630 | const oldHash = records.chunkModuleHashes[moduleKey];
|
---|
| 631 | const runtimes = chunkGraph.getModuleRuntimes(module);
|
---|
| 632 | if (oldRuntime === newRuntime && runtimes.has(newRuntime)) {
|
---|
| 633 | // Module is still in the same runtime combination
|
---|
| 634 | const hash = nonCodeGeneratedModules.has(module, newRuntime)
|
---|
| 635 | ? chunkGraph.getModuleHash(module, newRuntime)
|
---|
| 636 | : compilation.codeGenerationResults.getHash(
|
---|
| 637 | module,
|
---|
| 638 | newRuntime
|
---|
| 639 | );
|
---|
| 640 | if (hash !== oldHash) {
|
---|
| 641 | if (module.type === WEBPACK_MODULE_TYPE_RUNTIME) {
|
---|
| 642 | newRuntimeModules = newRuntimeModules || [];
|
---|
| 643 | newRuntimeModules.push(
|
---|
| 644 | /** @type {RuntimeModule} */ (module)
|
---|
| 645 | );
|
---|
| 646 | } else {
|
---|
| 647 | newModules = newModules || [];
|
---|
| 648 | newModules.push(module);
|
---|
| 649 | }
|
---|
| 650 | }
|
---|
| 651 | } else {
|
---|
| 652 | // module is no longer in this runtime combination
|
---|
| 653 | // We (incorrectly) assume that it's not in an overlapping runtime combination
|
---|
| 654 | // and dispose it from the main runtimes the chunk was removed from
|
---|
| 655 | forEachRuntime(removedFromRuntime, runtime => {
|
---|
| 656 | // If the module is still used in this runtime, do not dispose it
|
---|
| 657 | // This could create a bad runtime state where the module is still loaded,
|
---|
| 658 | // but no chunk which contains it. This means we don't receive further HMR updates
|
---|
| 659 | // to this module and that's bad.
|
---|
| 660 | // TODO force load one of the chunks which contains the module
|
---|
| 661 | for (const moduleRuntime of runtimes) {
|
---|
| 662 | if (typeof moduleRuntime === "string") {
|
---|
| 663 | if (moduleRuntime === runtime) return;
|
---|
| 664 | } else if (
|
---|
| 665 | moduleRuntime !== undefined &&
|
---|
| 666 | moduleRuntime.has(/** @type {string} */ (runtime))
|
---|
| 667 | )
|
---|
| 668 | return;
|
---|
| 669 | }
|
---|
| 670 | const item =
|
---|
| 671 | /** @type {HotUpdateMainContentByRuntimeItem} */ (
|
---|
| 672 | hotUpdateMainContentByRuntime.get(
|
---|
| 673 | /** @type {string} */ (runtime)
|
---|
| 674 | )
|
---|
| 675 | );
|
---|
| 676 | item.removedModules.add(module);
|
---|
| 677 | });
|
---|
| 678 | }
|
---|
| 679 | }
|
---|
| 680 | }
|
---|
| 681 | if (
|
---|
| 682 | (newModules && newModules.length > 0) ||
|
---|
| 683 | (newRuntimeModules && newRuntimeModules.length > 0)
|
---|
| 684 | ) {
|
---|
| 685 | const hotUpdateChunk = new HotUpdateChunk();
|
---|
| 686 | if (backCompat)
|
---|
| 687 | ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
|
---|
| 688 | hotUpdateChunk.id = chunkId;
|
---|
| 689 | hotUpdateChunk.runtime = currentChunk
|
---|
| 690 | ? currentChunk.runtime
|
---|
| 691 | : newRuntime;
|
---|
| 692 | if (currentChunk) {
|
---|
| 693 | for (const group of currentChunk.groupsIterable)
|
---|
| 694 | hotUpdateChunk.addGroup(group);
|
---|
| 695 | }
|
---|
| 696 | chunkGraph.attachModules(hotUpdateChunk, newModules || []);
|
---|
| 697 | chunkGraph.attachRuntimeModules(
|
---|
| 698 | hotUpdateChunk,
|
---|
| 699 | newRuntimeModules || []
|
---|
| 700 | );
|
---|
| 701 | if (newFullHashModules) {
|
---|
| 702 | chunkGraph.attachFullHashModules(
|
---|
| 703 | hotUpdateChunk,
|
---|
| 704 | newFullHashModules
|
---|
| 705 | );
|
---|
| 706 | }
|
---|
| 707 | if (newDependentHashModules) {
|
---|
| 708 | chunkGraph.attachDependentHashModules(
|
---|
| 709 | hotUpdateChunk,
|
---|
| 710 | newDependentHashModules
|
---|
| 711 | );
|
---|
| 712 | }
|
---|
| 713 | const renderManifest = compilation.getRenderManifest({
|
---|
| 714 | chunk: hotUpdateChunk,
|
---|
| 715 | hash: records.hash,
|
---|
| 716 | fullHash: records.hash,
|
---|
| 717 | outputOptions: compilation.outputOptions,
|
---|
| 718 | moduleTemplates: compilation.moduleTemplates,
|
---|
| 719 | dependencyTemplates: compilation.dependencyTemplates,
|
---|
| 720 | codeGenerationResults: compilation.codeGenerationResults,
|
---|
| 721 | runtimeTemplate: compilation.runtimeTemplate,
|
---|
| 722 | moduleGraph: compilation.moduleGraph,
|
---|
| 723 | chunkGraph
|
---|
| 724 | });
|
---|
| 725 | for (const entry of renderManifest) {
|
---|
| 726 | /** @type {string} */
|
---|
| 727 | let filename;
|
---|
| 728 | /** @type {AssetInfo} */
|
---|
| 729 | let assetInfo;
|
---|
| 730 | if ("filename" in entry) {
|
---|
| 731 | filename = entry.filename;
|
---|
| 732 | assetInfo = entry.info;
|
---|
| 733 | } else {
|
---|
| 734 | ({ path: filename, info: assetInfo } =
|
---|
| 735 | compilation.getPathWithInfo(
|
---|
| 736 | entry.filenameTemplate,
|
---|
| 737 | entry.pathOptions
|
---|
| 738 | ));
|
---|
| 739 | }
|
---|
| 740 | const source = entry.render();
|
---|
| 741 | compilation.additionalChunkAssets.push(filename);
|
---|
| 742 | compilation.emitAsset(filename, source, {
|
---|
| 743 | hotModuleReplacement: true,
|
---|
| 744 | ...assetInfo
|
---|
| 745 | });
|
---|
| 746 | if (currentChunk) {
|
---|
| 747 | currentChunk.files.add(filename);
|
---|
| 748 | compilation.hooks.chunkAsset.call(currentChunk, filename);
|
---|
| 749 | }
|
---|
| 750 | }
|
---|
| 751 | forEachRuntime(newRuntime, runtime => {
|
---|
| 752 | const item =
|
---|
| 753 | /** @type {HotUpdateMainContentByRuntimeItem} */ (
|
---|
| 754 | hotUpdateMainContentByRuntime.get(
|
---|
| 755 | /** @type {string} */ (runtime)
|
---|
| 756 | )
|
---|
| 757 | );
|
---|
| 758 | item.updatedChunkIds.add(/** @type {ChunkId} */ (chunkId));
|
---|
| 759 | });
|
---|
| 760 | }
|
---|
| 761 | }
|
---|
| 762 | const completelyRemovedModulesArray = Array.from(
|
---|
| 763 | completelyRemovedModules
|
---|
| 764 | );
|
---|
| 765 | const hotUpdateMainContentByFilename = new Map();
|
---|
| 766 | for (const {
|
---|
| 767 | removedChunkIds,
|
---|
| 768 | removedModules,
|
---|
| 769 | updatedChunkIds,
|
---|
| 770 | filename,
|
---|
| 771 | assetInfo
|
---|
| 772 | } of hotUpdateMainContentByRuntime.values()) {
|
---|
| 773 | const old = hotUpdateMainContentByFilename.get(filename);
|
---|
| 774 | if (
|
---|
| 775 | old &&
|
---|
| 776 | (!isSubset(old.removedChunkIds, removedChunkIds) ||
|
---|
| 777 | !isSubset(old.removedModules, removedModules) ||
|
---|
| 778 | !isSubset(old.updatedChunkIds, updatedChunkIds))
|
---|
| 779 | ) {
|
---|
| 780 | compilation.warnings.push(
|
---|
| 781 | new WebpackError(`HotModuleReplacementPlugin
|
---|
| 782 | The configured output.hotUpdateMainFilename doesn't lead to unique filenames per runtime and HMR update differs between runtimes.
|
---|
| 783 | This might lead to incorrect runtime behavior of the applied update.
|
---|
| 784 | To fix this, make sure to include [runtime] in the output.hotUpdateMainFilename option, or use the default config.`)
|
---|
| 785 | );
|
---|
| 786 | for (const chunkId of removedChunkIds)
|
---|
| 787 | old.removedChunkIds.add(chunkId);
|
---|
| 788 | for (const chunkId of removedModules)
|
---|
| 789 | old.removedModules.add(chunkId);
|
---|
| 790 | for (const chunkId of updatedChunkIds)
|
---|
| 791 | old.updatedChunkIds.add(chunkId);
|
---|
| 792 | continue;
|
---|
| 793 | }
|
---|
| 794 | hotUpdateMainContentByFilename.set(filename, {
|
---|
| 795 | removedChunkIds,
|
---|
| 796 | removedModules,
|
---|
| 797 | updatedChunkIds,
|
---|
| 798 | assetInfo
|
---|
| 799 | });
|
---|
| 800 | }
|
---|
| 801 | for (const [
|
---|
| 802 | filename,
|
---|
| 803 | { removedChunkIds, removedModules, updatedChunkIds, assetInfo }
|
---|
| 804 | ] of hotUpdateMainContentByFilename) {
|
---|
| 805 | const hotUpdateMainJson = {
|
---|
| 806 | c: Array.from(updatedChunkIds),
|
---|
| 807 | r: Array.from(removedChunkIds),
|
---|
| 808 | m:
|
---|
| 809 | removedModules.size === 0
|
---|
| 810 | ? completelyRemovedModulesArray
|
---|
| 811 | : completelyRemovedModulesArray.concat(
|
---|
| 812 | Array.from(
|
---|
| 813 | removedModules,
|
---|
| 814 | m =>
|
---|
| 815 | /** @type {ModuleId} */ (chunkGraph.getModuleId(m))
|
---|
| 816 | )
|
---|
| 817 | )
|
---|
| 818 | };
|
---|
| 819 |
|
---|
| 820 | const source = new RawSource(JSON.stringify(hotUpdateMainJson));
|
---|
| 821 | compilation.emitAsset(filename, source, {
|
---|
| 822 | hotModuleReplacement: true,
|
---|
| 823 | ...assetInfo
|
---|
| 824 | });
|
---|
| 825 | }
|
---|
| 826 | }
|
---|
| 827 | );
|
---|
| 828 |
|
---|
| 829 | compilation.hooks.additionalTreeRuntimeRequirements.tap(
|
---|
| 830 | PLUGIN_NAME,
|
---|
| 831 | (chunk, runtimeRequirements) => {
|
---|
| 832 | runtimeRequirements.add(RuntimeGlobals.hmrDownloadManifest);
|
---|
| 833 | runtimeRequirements.add(RuntimeGlobals.hmrDownloadUpdateHandlers);
|
---|
| 834 | runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
|
---|
| 835 | runtimeRequirements.add(RuntimeGlobals.moduleCache);
|
---|
| 836 | compilation.addRuntimeModule(
|
---|
| 837 | chunk,
|
---|
| 838 | new HotModuleReplacementRuntimeModule()
|
---|
| 839 | );
|
---|
| 840 | }
|
---|
| 841 | );
|
---|
| 842 |
|
---|
| 843 | normalModuleFactory.hooks.parser
|
---|
| 844 | .for(JAVASCRIPT_MODULE_TYPE_AUTO)
|
---|
| 845 | .tap(PLUGIN_NAME, parser => {
|
---|
| 846 | applyModuleHot(parser);
|
---|
| 847 | applyImportMetaHot(parser);
|
---|
| 848 | });
|
---|
| 849 | normalModuleFactory.hooks.parser
|
---|
| 850 | .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
|
---|
| 851 | .tap(PLUGIN_NAME, parser => {
|
---|
| 852 | applyModuleHot(parser);
|
---|
| 853 | });
|
---|
| 854 | normalModuleFactory.hooks.parser
|
---|
| 855 | .for(JAVASCRIPT_MODULE_TYPE_ESM)
|
---|
| 856 | .tap(PLUGIN_NAME, parser => {
|
---|
| 857 | applyImportMetaHot(parser);
|
---|
| 858 | });
|
---|
| 859 | normalModuleFactory.hooks.module.tap(PLUGIN_NAME, module => {
|
---|
| 860 | module.hot = true;
|
---|
| 861 | return module;
|
---|
| 862 | });
|
---|
| 863 |
|
---|
| 864 | NormalModule.getCompilationHooks(compilation).loader.tap(
|
---|
| 865 | PLUGIN_NAME,
|
---|
| 866 | context => {
|
---|
| 867 | context.hot = true;
|
---|
| 868 | }
|
---|
| 869 | );
|
---|
| 870 | }
|
---|
| 871 | );
|
---|
| 872 | }
|
---|
| 873 | }
|
---|
| 874 |
|
---|
| 875 | module.exports = HotModuleReplacementPlugin;
|
---|