提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

894 lines
30 KiB

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