HotModuleReplacementPlugin.js 32 KB

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