HotModuleReplacementPlugin.js 29 KB

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