HotModuleReplacementPlugin.js 32 KB

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