HotModuleReplacementPlugin.js 26 KB

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