source: frontend/node_modules/webpack/lib/HotModuleReplacementPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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