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