HotModuleReplacementPlugin.js 30 KB

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