HotModuleReplacementPlugin.js 31 KB

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