Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

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