HotModuleReplacementPlugin.js 26 KB

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