HotModuleReplacementPlugin.js 26 KB

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