1 |
|
2 |
|
3 |
|
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 ConstDependency = require("./dependencies/ConstDependency");
|
16 | const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
|
17 | const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
|
18 | const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
|
19 | const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
|
20 | const HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule");
|
21 | const JavascriptParser = require("./javascript/JavascriptParser");
|
22 | const {
|
23 | evaluateToIdentifier
|
24 | } = require("./javascript/JavascriptParserHelpers");
|
25 | const { find } = require("./util/SetHelpers");
|
26 | const TupleSet = require("./util/TupleSet");
|
27 | const { compareModulesById } = require("./util/comparators");
|
28 | const { getRuntimeKey, keyToRuntime } = require("./util/runtime");
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | const parserHooksMap = new WeakMap();
|
43 |
|
44 | class HotModuleReplacementPlugin {
|
45 | |
46 |
|
47 |
|
48 |
|
49 | static getParserHooks(parser) {
|
50 | if (!(parser instanceof JavascriptParser)) {
|
51 | throw new TypeError(
|
52 | "The 'parser' argument must be an instance of JavascriptParser"
|
53 | );
|
54 | }
|
55 | let hooks = parserHooksMap.get(parser);
|
56 | if (hooks === undefined) {
|
57 | hooks = {
|
58 | hotAcceptCallback: new SyncBailHook(["expression", "requests"]),
|
59 | hotAcceptWithoutCallback: new SyncBailHook(["expression", "requests"])
|
60 | };
|
61 | parserHooksMap.set(parser, hooks);
|
62 | }
|
63 | return hooks;
|
64 | }
|
65 |
|
66 | constructor(options) {
|
67 | this.options = options || {};
|
68 | }
|
69 |
|
70 | |
71 |
|
72 |
|
73 |
|
74 |
|
75 | apply(compiler) {
|
76 | const runtimeRequirements = [RuntimeGlobals.module];
|
77 |
|
78 | const createAcceptHandler = (parser, ParamDependency) => {
|
79 | const {
|
80 | hotAcceptCallback,
|
81 | hotAcceptWithoutCallback
|
82 | } = HotModuleReplacementPlugin.getParserHooks(parser);
|
83 |
|
84 | return expr => {
|
85 | const module = parser.state.module;
|
86 | const dep = new ConstDependency(
|
87 | `${module.moduleArgument}.hot.accept`,
|
88 | expr.callee.range,
|
89 | runtimeRequirements
|
90 | );
|
91 | dep.loc = expr.loc;
|
92 | module.addPresentationalDependency(dep);
|
93 | module.buildInfo.moduleConcatenationBailout = "Hot Module Replacement";
|
94 | if (expr.arguments.length >= 1) {
|
95 | const arg = parser.evaluateExpression(expr.arguments[0]);
|
96 | let params = [];
|
97 | let requests = [];
|
98 | if (arg.isString()) {
|
99 | params = [arg];
|
100 | } else if (arg.isArray()) {
|
101 | params = arg.items.filter(param => param.isString());
|
102 | }
|
103 | if (params.length > 0) {
|
104 | params.forEach((param, idx) => {
|
105 | const request = param.string;
|
106 | const dep = new ParamDependency(request, param.range);
|
107 | dep.optional = true;
|
108 | dep.loc = Object.create(expr.loc);
|
109 | dep.loc.index = idx;
|
110 | module.addDependency(dep);
|
111 | requests.push(request);
|
112 | });
|
113 | if (expr.arguments.length > 1) {
|
114 | hotAcceptCallback.call(expr.arguments[1], requests);
|
115 | parser.walkExpression(expr.arguments[1]);
|
116 | return true;
|
117 | } else {
|
118 | hotAcceptWithoutCallback.call(expr, requests);
|
119 | return true;
|
120 | }
|
121 | }
|
122 | }
|
123 | parser.walkExpressions(expr.arguments);
|
124 | return true;
|
125 | };
|
126 | };
|
127 |
|
128 | const createDeclineHandler = (parser, ParamDependency) => expr => {
|
129 | const module = parser.state.module;
|
130 | const dep = new ConstDependency(
|
131 | `${module.moduleArgument}.hot.decline`,
|
132 | expr.callee.range,
|
133 | runtimeRequirements
|
134 | );
|
135 | dep.loc = expr.loc;
|
136 | module.addPresentationalDependency(dep);
|
137 | module.buildInfo.moduleConcatenationBailout = "Hot Module Replacement";
|
138 | if (expr.arguments.length === 1) {
|
139 | const arg = parser.evaluateExpression(expr.arguments[0]);
|
140 | let params = [];
|
141 | if (arg.isString()) {
|
142 | params = [arg];
|
143 | } else if (arg.isArray()) {
|
144 | params = arg.items.filter(param => param.isString());
|
145 | }
|
146 | params.forEach((param, idx) => {
|
147 | const dep = new ParamDependency(param.string, param.range);
|
148 | dep.optional = true;
|
149 | dep.loc = Object.create(expr.loc);
|
150 | dep.loc.index = idx;
|
151 | module.addDependency(dep);
|
152 | });
|
153 | }
|
154 | return true;
|
155 | };
|
156 |
|
157 | const createHMRExpressionHandler = parser => expr => {
|
158 | const module = parser.state.module;
|
159 | const dep = new ConstDependency(
|
160 | `${module.moduleArgument}.hot`,
|
161 | expr.range,
|
162 | runtimeRequirements
|
163 | );
|
164 | dep.loc = expr.loc;
|
165 | module.addPresentationalDependency(dep);
|
166 | module.buildInfo.moduleConcatenationBailout = "Hot Module Replacement";
|
167 | return true;
|
168 | };
|
169 |
|
170 | const applyModuleHot = parser => {
|
171 | parser.hooks.evaluateIdentifier.for("module.hot").tap(
|
172 | {
|
173 | name: "HotModuleReplacementPlugin",
|
174 | before: "NodeStuffPlugin"
|
175 | },
|
176 | expr => {
|
177 | return evaluateToIdentifier(
|
178 | "module.hot",
|
179 | "module",
|
180 | () => ["hot"],
|
181 | true
|
182 | )(expr);
|
183 | }
|
184 | );
|
185 | parser.hooks.call
|
186 | .for("module.hot.accept")
|
187 | .tap(
|
188 | "HotModuleReplacementPlugin",
|
189 | createAcceptHandler(parser, ModuleHotAcceptDependency)
|
190 | );
|
191 | parser.hooks.call
|
192 | .for("module.hot.decline")
|
193 | .tap(
|
194 | "HotModuleReplacementPlugin",
|
195 | createDeclineHandler(parser, ModuleHotDeclineDependency)
|
196 | );
|
197 | parser.hooks.expression
|
198 | .for("module.hot")
|
199 | .tap("HotModuleReplacementPlugin", createHMRExpressionHandler(parser));
|
200 | };
|
201 |
|
202 | const applyImportMetaHot = parser => {
|
203 | parser.hooks.evaluateIdentifier
|
204 | .for("import.meta.webpackHot")
|
205 | .tap("HotModuleReplacementPlugin", expr => {
|
206 | return evaluateToIdentifier(
|
207 | "import.meta.webpackHot",
|
208 | "import.meta",
|
209 | () => ["webpackHot"],
|
210 | true
|
211 | )(expr);
|
212 | });
|
213 | parser.hooks.call
|
214 | .for("import.meta.webpackHot.accept")
|
215 | .tap(
|
216 | "HotModuleReplacementPlugin",
|
217 | createAcceptHandler(parser, ImportMetaHotAcceptDependency)
|
218 | );
|
219 | parser.hooks.call
|
220 | .for("import.meta.webpackHot.decline")
|
221 | .tap(
|
222 | "HotModuleReplacementPlugin",
|
223 | createDeclineHandler(parser, ImportMetaHotDeclineDependency)
|
224 | );
|
225 | parser.hooks.expression
|
226 | .for("import.meta.webpackHot")
|
227 | .tap("HotModuleReplacementPlugin", createHMRExpressionHandler(parser));
|
228 | };
|
229 |
|
230 | compiler.hooks.compilation.tap(
|
231 | "HotModuleReplacementPlugin",
|
232 | (compilation, { normalModuleFactory }) => {
|
233 |
|
234 |
|
235 | if (compilation.compiler !== compiler) return;
|
236 |
|
237 |
|
238 | compilation.dependencyFactories.set(
|
239 | ModuleHotAcceptDependency,
|
240 | normalModuleFactory
|
241 | );
|
242 | compilation.dependencyTemplates.set(
|
243 | ModuleHotAcceptDependency,
|
244 | new ModuleHotAcceptDependency.Template()
|
245 | );
|
246 | compilation.dependencyFactories.set(
|
247 | ModuleHotDeclineDependency,
|
248 | normalModuleFactory
|
249 | );
|
250 | compilation.dependencyTemplates.set(
|
251 | ModuleHotDeclineDependency,
|
252 | new ModuleHotDeclineDependency.Template()
|
253 | );
|
254 |
|
255 |
|
256 |
|
257 | compilation.dependencyFactories.set(
|
258 | ImportMetaHotAcceptDependency,
|
259 | normalModuleFactory
|
260 | );
|
261 | compilation.dependencyTemplates.set(
|
262 | ImportMetaHotAcceptDependency,
|
263 | new ImportMetaHotAcceptDependency.Template()
|
264 | );
|
265 | compilation.dependencyFactories.set(
|
266 | ImportMetaHotDeclineDependency,
|
267 | normalModuleFactory
|
268 | );
|
269 | compilation.dependencyTemplates.set(
|
270 | ImportMetaHotDeclineDependency,
|
271 | new ImportMetaHotDeclineDependency.Template()
|
272 | );
|
273 |
|
274 |
|
275 | let hotIndex = 0;
|
276 | const fullHashChunkModuleHashes = {};
|
277 | const chunkModuleHashes = {};
|
278 |
|
279 | compilation.hooks.record.tap(
|
280 | "HotModuleReplacementPlugin",
|
281 | (compilation, records) => {
|
282 | if (records.hash === compilation.hash) return;
|
283 | const chunkGraph = compilation.chunkGraph;
|
284 | records.hash = compilation.hash;
|
285 | records.hotIndex = hotIndex;
|
286 | records.fullHashChunkModuleHashes = fullHashChunkModuleHashes;
|
287 | records.chunkModuleHashes = chunkModuleHashes;
|
288 | records.chunkHashs = {};
|
289 | records.chunkRuntime = {};
|
290 | for (const chunk of compilation.chunks) {
|
291 | records.chunkHashs[chunk.id] = chunk.hash;
|
292 | records.chunkRuntime[chunk.id] = getRuntimeKey(chunk.runtime);
|
293 | }
|
294 | records.chunkModuleIds = {};
|
295 | for (const chunk of compilation.chunks) {
|
296 | records.chunkModuleIds[
|
297 | chunk.id
|
298 | ] = Array.from(
|
299 | chunkGraph.getOrderedChunkModulesIterable(
|
300 | chunk,
|
301 | compareModulesById(chunkGraph)
|
302 | ),
|
303 | m => chunkGraph.getModuleId(m)
|
304 | );
|
305 | }
|
306 | }
|
307 | );
|
308 |
|
309 | const updatedModules = new TupleSet();
|
310 |
|
311 | const lazyHashedModules = new TupleSet();
|
312 | compilation.hooks.fullHash.tap("HotModuleReplacementPlugin", hash => {
|
313 | const chunkGraph = compilation.chunkGraph;
|
314 | const records = compilation.records;
|
315 | for (const chunk of compilation.chunks) {
|
316 |
|
317 | const lazyHashedModulesInThisChunk = new Set();
|
318 | const fullHashModules = chunkGraph.getChunkFullHashModulesIterable(
|
319 | chunk
|
320 | );
|
321 | if (fullHashModules !== undefined) {
|
322 | for (const module of fullHashModules) {
|
323 | lazyHashedModules.add(module, chunk);
|
324 | lazyHashedModulesInThisChunk.add(module);
|
325 | }
|
326 | }
|
327 | const modules = chunkGraph.getChunkModulesIterable(chunk);
|
328 | if (modules !== undefined) {
|
329 | if (
|
330 | records.chunkModuleHashes &&
|
331 | records.fullHashChunkModuleHashes
|
332 | ) {
|
333 | for (const module of modules) {
|
334 | const key = `${chunk.id}|${module.identifier()}`;
|
335 | const hash = chunkGraph.getModuleHash(module, chunk.runtime);
|
336 | if (lazyHashedModulesInThisChunk.has(module)) {
|
337 | if (records.fullHashChunkModuleHashes[key] !== hash) {
|
338 | updatedModules.add(module, chunk);
|
339 | }
|
340 | fullHashChunkModuleHashes[key] = hash;
|
341 | } else {
|
342 | if (records.chunkModuleHashes[key] !== hash) {
|
343 | updatedModules.add(module, chunk);
|
344 | }
|
345 | chunkModuleHashes[key] = hash;
|
346 | }
|
347 | }
|
348 | } else {
|
349 | for (const module of modules) {
|
350 | const key = `${chunk.id}|${module.identifier()}`;
|
351 | const hash = chunkGraph.getModuleHash(module, chunk.runtime);
|
352 | if (lazyHashedModulesInThisChunk.has(module)) {
|
353 | fullHashChunkModuleHashes[key] = hash;
|
354 | } else {
|
355 | chunkModuleHashes[key] = hash;
|
356 | }
|
357 | }
|
358 | }
|
359 | }
|
360 | }
|
361 |
|
362 | hotIndex = records.hotIndex || 0;
|
363 | if (updatedModules.size > 0) hotIndex++;
|
364 |
|
365 | hash.update(`${hotIndex}`);
|
366 | });
|
367 | compilation.hooks.processAssets.tap(
|
368 | {
|
369 | name: "HotModuleReplacementPlugin",
|
370 | stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
|
371 | },
|
372 | () => {
|
373 | const chunkGraph = compilation.chunkGraph;
|
374 | const records = compilation.records;
|
375 | if (records.hash === compilation.hash) return;
|
376 | if (
|
377 | !records.chunkModuleHashes ||
|
378 | !records.chunkHashs ||
|
379 | !records.chunkModuleIds
|
380 | ) {
|
381 | return;
|
382 | }
|
383 | for (const [module, chunk] of lazyHashedModules) {
|
384 | const key = `${chunk.id}|${module.identifier()}`;
|
385 | const hash = chunkGraph.getModuleHash(module, chunk.runtime);
|
386 | if (records.chunkModuleHashes[key] !== hash) {
|
387 | updatedModules.add(module, chunk);
|
388 | }
|
389 | chunkModuleHashes[key] = hash;
|
390 | }
|
391 | const hotUpdateMainContent = {
|
392 | c: [],
|
393 | r: [],
|
394 | m: undefined
|
395 | };
|
396 |
|
397 |
|
398 |
|
399 | const allModules = new Map();
|
400 | for (const module of compilation.modules) {
|
401 | allModules.set(chunkGraph.getModuleId(module), module);
|
402 | }
|
403 |
|
404 |
|
405 | const allRemovedModules = new Set();
|
406 |
|
407 | for (const key of Object.keys(records.chunkHashs)) {
|
408 |
|
409 | for (const id of records.chunkModuleIds[key]) {
|
410 | if (!allModules.has(id)) {
|
411 | allRemovedModules.add(id);
|
412 | }
|
413 | }
|
414 |
|
415 | let chunkId;
|
416 | let newModules;
|
417 | let newRuntimeModules;
|
418 | let newFullHashModules;
|
419 | let newRuntime;
|
420 | const currentChunk = find(
|
421 | compilation.chunks,
|
422 | chunk => `${chunk.id}` === key
|
423 | );
|
424 | if (currentChunk) {
|
425 | chunkId = currentChunk.id;
|
426 | newRuntime = currentChunk.runtime;
|
427 | newModules = chunkGraph
|
428 | .getChunkModules(currentChunk)
|
429 | .filter(module => updatedModules.has(module, currentChunk));
|
430 | newRuntimeModules = Array.from(
|
431 | chunkGraph.getChunkRuntimeModulesIterable(currentChunk)
|
432 | ).filter(module => updatedModules.has(module, currentChunk));
|
433 | const fullHashModules = chunkGraph.getChunkFullHashModulesIterable(
|
434 | currentChunk
|
435 | );
|
436 | newFullHashModules =
|
437 | fullHashModules &&
|
438 | Array.from(fullHashModules).filter(module =>
|
439 | updatedModules.has(module, currentChunk)
|
440 | );
|
441 | } else {
|
442 | chunkId = `${+key}` === key ? +key : key;
|
443 | hotUpdateMainContent.r.push(chunkId);
|
444 | const runtime = keyToRuntime(records.chunkRuntime[key]);
|
445 | for (const id of records.chunkModuleIds[key]) {
|
446 | const module = allModules.get(id);
|
447 | if (!module) continue;
|
448 | const hash = chunkGraph.getModuleHash(module, runtime);
|
449 | const moduleKey = `${key}|${module.identifier()}`;
|
450 | if (hash !== records.chunkModuleHashes[moduleKey]) {
|
451 | newModules = newModules || [];
|
452 | newModules.push(module);
|
453 | }
|
454 | }
|
455 | }
|
456 | if (
|
457 | (newModules && newModules.length > 0) ||
|
458 | (newRuntimeModules && newRuntimeModules.length > 0)
|
459 | ) {
|
460 | const hotUpdateChunk = new HotUpdateChunk();
|
461 | ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
|
462 | hotUpdateChunk.id = chunkId;
|
463 | hotUpdateChunk.runtime = newRuntime;
|
464 | if (currentChunk) {
|
465 | for (const group of currentChunk.groupsIterable)
|
466 | hotUpdateChunk.addGroup(group);
|
467 | }
|
468 | chunkGraph.attachModules(hotUpdateChunk, newModules || []);
|
469 | chunkGraph.attachRuntimeModules(
|
470 | hotUpdateChunk,
|
471 | newRuntimeModules || []
|
472 | );
|
473 | if (newFullHashModules) {
|
474 | chunkGraph.attachFullHashModules(
|
475 | hotUpdateChunk,
|
476 | newFullHashModules
|
477 | );
|
478 | }
|
479 | const renderManifest = compilation.getRenderManifest({
|
480 | chunk: hotUpdateChunk,
|
481 | hash: records.hash,
|
482 | fullHash: records.hash,
|
483 | outputOptions: compilation.outputOptions,
|
484 | moduleTemplates: compilation.moduleTemplates,
|
485 | dependencyTemplates: compilation.dependencyTemplates,
|
486 | codeGenerationResults: compilation.codeGenerationResults,
|
487 | runtimeTemplate: compilation.runtimeTemplate,
|
488 | moduleGraph: compilation.moduleGraph,
|
489 | chunkGraph
|
490 | });
|
491 | for (const entry of renderManifest) {
|
492 |
|
493 | let filename;
|
494 |
|
495 | let assetInfo;
|
496 | if ("filename" in entry) {
|
497 | filename = entry.filename;
|
498 | assetInfo = entry.info;
|
499 | } else {
|
500 | ({
|
501 | path: filename,
|
502 | info: assetInfo
|
503 | } = compilation.getPathWithInfo(
|
504 | entry.filenameTemplate,
|
505 | entry.pathOptions
|
506 | ));
|
507 | }
|
508 | const source = entry.render();
|
509 | compilation.additionalChunkAssets.push(filename);
|
510 | compilation.emitAsset(filename, source, {
|
511 | hotModuleReplacement: true,
|
512 | ...assetInfo
|
513 | });
|
514 | if (currentChunk) {
|
515 | currentChunk.files.add(filename);
|
516 | compilation.hooks.chunkAsset.call(currentChunk, filename);
|
517 | }
|
518 | }
|
519 | hotUpdateMainContent.c.push(chunkId);
|
520 | }
|
521 | }
|
522 | hotUpdateMainContent.m = Array.from(allRemovedModules);
|
523 | const source = new RawSource(JSON.stringify(hotUpdateMainContent));
|
524 | const {
|
525 | path: filename,
|
526 | info: assetInfo
|
527 | } = compilation.getPathWithInfo(
|
528 | compilation.outputOptions.hotUpdateMainFilename,
|
529 | {
|
530 | hash: records.hash
|
531 | }
|
532 | );
|
533 | compilation.emitAsset(filename, source, {
|
534 | hotModuleReplacement: true,
|
535 | ...assetInfo
|
536 | });
|
537 | }
|
538 | );
|
539 |
|
540 | compilation.hooks.additionalTreeRuntimeRequirements.tap(
|
541 | "HotModuleReplacementPlugin",
|
542 | (chunk, runtimeRequirements) => {
|
543 | runtimeRequirements.add(RuntimeGlobals.hmrDownloadManifest);
|
544 | runtimeRequirements.add(RuntimeGlobals.hmrDownloadUpdateHandlers);
|
545 | runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
|
546 | runtimeRequirements.add(RuntimeGlobals.moduleCache);
|
547 | compilation.addRuntimeModule(
|
548 | chunk,
|
549 | new HotModuleReplacementRuntimeModule()
|
550 | );
|
551 | }
|
552 | );
|
553 |
|
554 | normalModuleFactory.hooks.parser
|
555 | .for("javascript/auto")
|
556 | .tap("HotModuleReplacementPlugin", parser => {
|
557 | applyModuleHot(parser);
|
558 | applyImportMetaHot(parser);
|
559 | });
|
560 | normalModuleFactory.hooks.parser
|
561 | .for("javascript/dynamic")
|
562 | .tap("HotModuleReplacementPlugin", parser => {
|
563 | applyModuleHot(parser);
|
564 | });
|
565 | normalModuleFactory.hooks.parser
|
566 | .for("javascript/esm")
|
567 | .tap("HotModuleReplacementPlugin", parser => {
|
568 | applyImportMetaHot(parser);
|
569 | });
|
570 |
|
571 | NormalModule.getCompilationHooks(compilation).loader.tap(
|
572 | "HotModuleReplacementPlugin",
|
573 | context => {
|
574 | context.hot = true;
|
575 | }
|
576 | );
|
577 | }
|
578 | );
|
579 | }
|
580 | }
|
581 |
|
582 | module.exports = HotModuleReplacementPlugin;
|