ModuleChunkFormatPlugin.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { ConcatSource } = require("webpack-sources");
  7. const { HotUpdateChunk, RuntimeGlobals } = require("..");
  8. const { JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants");
  9. const Template = require("../Template");
  10. const {
  11. createChunkHashHandler,
  12. getChunkInfo
  13. } = require("../javascript/ChunkFormatHelpers");
  14. const { getAllChunks } = require("../javascript/ChunkHelpers");
  15. const {
  16. chunkHasJs,
  17. getChunkFilenameTemplate,
  18. getCompilationHooks
  19. } = require("../javascript/JavascriptModulesPlugin");
  20. const { entryModuleIdExpression } = require("../javascript/StartupHelpers");
  21. const { getUndoPath } = require("../util/identifier");
  22. /** @import { Source } from "webpack-sources" */
  23. /** @import Chunk from "../Chunk" */
  24. /** @import ChunkGraph from "../ChunkGraph" */
  25. /** @import Compilation from "../Compilation" */
  26. /** @import Compiler from "../Compiler" */
  27. /** @import Entrypoint from "../Entrypoint" */
  28. /**
  29. * @import {
  30. * ChunkRenderContext
  31. * } from "../javascript/JavascriptModulesPlugin"
  32. */
  33. /**
  34. * Gets relative path.
  35. * @param {Compilation} compilation the compilation instance
  36. * @param {Chunk} chunk the chunk
  37. * @param {Chunk} runtimeChunk the runtime chunk
  38. * @returns {string} the relative path
  39. */
  40. const getRelativePath = (compilation, chunk, runtimeChunk) => {
  41. const currentOutputName = compilation
  42. .getPath(
  43. getChunkFilenameTemplate(runtimeChunk, compilation.outputOptions),
  44. {
  45. chunk: runtimeChunk,
  46. contentHashType: "javascript"
  47. }
  48. )
  49. .replace(/^\/+/g, "")
  50. .split("/");
  51. const baseOutputName = [...currentOutputName];
  52. const chunkOutputName = compilation
  53. .getPath(getChunkFilenameTemplate(chunk, compilation.outputOptions), {
  54. chunk,
  55. contentHashType: "javascript"
  56. })
  57. .replace(/^\/+/g, "")
  58. .split("/");
  59. // remove common parts except filename
  60. while (
  61. baseOutputName.length > 1 &&
  62. chunkOutputName.length > 1 &&
  63. baseOutputName[0] === chunkOutputName[0]
  64. ) {
  65. baseOutputName.shift();
  66. chunkOutputName.shift();
  67. }
  68. const last = chunkOutputName.join("/");
  69. // create final path
  70. return getUndoPath(baseOutputName.join("/"), last, true) + last;
  71. };
  72. /**
  73. * Renders chunk import.
  74. * @param {Compilation} compilation the compilation instance
  75. * @param {Chunk} chunk the chunk to render the import for
  76. * @param {string=} namedImport the named import to use for the import
  77. * @param {Chunk=} runtimeChunk the runtime chunk
  78. * @returns {string} the import source
  79. */
  80. function renderChunkImport(compilation, chunk, namedImport, runtimeChunk) {
  81. return `import ${
  82. namedImport ? `* as ${namedImport}` : `{ ${RuntimeGlobals.require} }`
  83. } from ${JSON.stringify(
  84. getRelativePath(compilation, chunk, runtimeChunk || chunk)
  85. )};\n`;
  86. }
  87. /**
  88. * Gets chunk named import.
  89. * @param {number} index the index of the chunk
  90. * @returns {string} the named import to use for the import
  91. */
  92. function getChunkNamedImport(index) {
  93. return `__webpack_chunk_${index}__`;
  94. }
  95. const PLUGIN_NAME = "ModuleChunkFormatPlugin";
  96. class ModuleChunkFormatPlugin {
  97. /**
  98. * Applies the plugin by registering its hooks on the compiler.
  99. * @param {Compiler} compiler the compiler instance
  100. * @returns {void}
  101. */
  102. apply(compiler) {
  103. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  104. compilation.hooks.additionalChunkRuntimeRequirements.tap(
  105. PLUGIN_NAME,
  106. (chunk, set) => {
  107. if (chunk.hasRuntime()) return;
  108. if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
  109. set.add(RuntimeGlobals.require);
  110. set.add(RuntimeGlobals.externalInstallChunk);
  111. }
  112. }
  113. );
  114. const hooks = getCompilationHooks(compilation);
  115. /**
  116. * With dependent chunks.
  117. * @param {Iterable<Chunk>} chunks the chunks to render
  118. * @param {ChunkGraph} chunkGraph the chunk graph
  119. * @param {Chunk=} runtimeChunk the runtime chunk
  120. * @returns {Source | undefined} the source
  121. */
  122. const withDependentChunks = (chunks, chunkGraph, runtimeChunk) => {
  123. if (/** @type {Set<Chunk>} */ (chunks).size > 0) {
  124. const source = new ConcatSource();
  125. let index = 0;
  126. for (const chunk of chunks) {
  127. index++;
  128. if (!chunkHasJs(chunk, chunkGraph)) {
  129. continue;
  130. }
  131. const namedImport = getChunkNamedImport(index);
  132. source.add(
  133. renderChunkImport(
  134. compilation,
  135. chunk,
  136. namedImport,
  137. runtimeChunk || chunk
  138. )
  139. );
  140. source.add(
  141. `${RuntimeGlobals.externalInstallChunk}(${namedImport});\n`
  142. );
  143. }
  144. return source;
  145. }
  146. };
  147. hooks.renderStartup.tap(
  148. PLUGIN_NAME,
  149. (modules, _lastModule, renderContext) => {
  150. const { chunk, chunkGraph } = renderContext;
  151. if (
  152. chunkGraph.getNumberOfEntryModules(chunk) > 0 &&
  153. chunk.hasRuntime()
  154. ) {
  155. const entryDependentChunks =
  156. chunkGraph.getChunkEntryDependentChunksIterable(chunk);
  157. const sourceWithDependentChunks = withDependentChunks(
  158. entryDependentChunks,
  159. chunkGraph,
  160. chunk
  161. );
  162. if (!sourceWithDependentChunks) {
  163. return modules;
  164. }
  165. if (modules.size() === 0) {
  166. return sourceWithDependentChunks;
  167. }
  168. const source = new ConcatSource();
  169. source.add(sourceWithDependentChunks);
  170. source.add("\n");
  171. source.add(modules);
  172. return source;
  173. }
  174. return modules;
  175. }
  176. );
  177. hooks.renderChunk.tap(PLUGIN_NAME, (modules, renderContext) => {
  178. const { chunk, chunkGraph, runtimeTemplate } = renderContext;
  179. const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null;
  180. const source = new ConcatSource();
  181. const cst = runtimeTemplate.renderConst();
  182. source.add(
  183. `export ${cst} ${RuntimeGlobals.esmIds} = ${JSON.stringify(
  184. chunk.ids
  185. )};\n`
  186. );
  187. source.add(`export ${cst} ${RuntimeGlobals.esmModules} = `);
  188. source.add(modules);
  189. source.add(";\n");
  190. const runtimeModules = chunkGraph.getChunkRuntimeModulesInOrder(chunk);
  191. if (runtimeModules.length > 0) {
  192. source.add(`export ${cst} ${RuntimeGlobals.esmRuntime} =\n`);
  193. source.add(
  194. Template.renderChunkRuntimeModules(runtimeModules, renderContext)
  195. );
  196. }
  197. if (hotUpdateChunk) {
  198. return source;
  199. }
  200. const { entries, runtimeChunk } = getChunkInfo(chunk, chunkGraph);
  201. if (runtimeChunk) {
  202. const { inlinedEntryModule, inlinedEntrySource } =
  203. /** @type {ChunkRenderContext} */ (renderContext);
  204. const entrySource = new ConcatSource();
  205. entrySource.add(source);
  206. entrySource.add(";\n\n// load runtime\n");
  207. entrySource.add(
  208. renderChunkImport(compilation, runtimeChunk, "", chunk)
  209. );
  210. const startupSource = new ConcatSource();
  211. // The `__webpack_exec__` helper is only needed for entries that
  212. // are executed through the registry, not for inlined entries.
  213. const needExec = entries.some(
  214. ([module]) =>
  215. module !== inlinedEntryModule &&
  216. chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)
  217. );
  218. if (needExec) {
  219. startupSource.add(
  220. `var __webpack_exec__ = ${runtimeTemplate.returningFunction(
  221. `${RuntimeGlobals.require}(${entryModuleIdExpression(
  222. chunkGraph,
  223. chunk,
  224. "moduleId"
  225. )})`,
  226. "moduleId"
  227. )}\n`
  228. );
  229. }
  230. /** @type {Set<Chunk>} */
  231. const loadedChunks = new Set();
  232. let ownInstalled = false;
  233. for (let i = 0; i < entries.length; i++) {
  234. const [module, entrypoint] = entries[i];
  235. if (!chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
  236. continue;
  237. }
  238. const final = i + 1 === entries.length;
  239. const moduleId = chunkGraph.getModuleId(module);
  240. const chunks = getAllChunks(
  241. /** @type {Entrypoint} */ (entrypoint),
  242. chunk,
  243. /** @type {Chunk} */ (runtimeChunk)
  244. );
  245. /** @type {Set<Chunk>} */
  246. const processChunks = new Set();
  247. for (const chunk of chunks) {
  248. if (loadedChunks.has(chunk)) {
  249. continue;
  250. }
  251. loadedChunks.add(chunk);
  252. processChunks.add(chunk);
  253. }
  254. const sourceWithDependentChunks = withDependentChunks(
  255. processChunks,
  256. chunkGraph,
  257. chunk
  258. );
  259. if (sourceWithDependentChunks) {
  260. startupSource.add("\n");
  261. startupSource.add(sourceWithDependentChunks);
  262. }
  263. if (!ownInstalled) {
  264. ownInstalled = true;
  265. // Own chunk data comes from the `export const` bindings above.
  266. startupSource.add(
  267. `${RuntimeGlobals.externalInstallChunk}({ ${
  268. RuntimeGlobals.esmIds
  269. }, ${RuntimeGlobals.esmModules}${
  270. runtimeModules.length > 0
  271. ? `, ${RuntimeGlobals.esmRuntime}`
  272. : ""
  273. } });\n`
  274. );
  275. }
  276. if (module === inlinedEntryModule && inlinedEntrySource) {
  277. // Inline the entry at the top level so its declarations stay
  278. // live ESM bindings (exports appended by `renderStartup`).
  279. startupSource.add(
  280. `${runtimeTemplate.renderLet()} ${
  281. RuntimeGlobals.exports
  282. } = {};\n`
  283. );
  284. startupSource.add(inlinedEntrySource);
  285. startupSource.add("\n");
  286. } else {
  287. startupSource.add(
  288. `${
  289. final ? `var ${RuntimeGlobals.exports} = ` : ""
  290. }__webpack_exec__(${JSON.stringify(moduleId)});\n`
  291. );
  292. }
  293. }
  294. entrySource.add(
  295. hooks.renderStartup.call(
  296. startupSource,
  297. entries[entries.length - 1][0],
  298. inlinedEntryModule
  299. ? { ...renderContext, inlined: true, inlinedInIIFE: false }
  300. : renderContext
  301. )
  302. );
  303. return entrySource;
  304. }
  305. return source;
  306. });
  307. hooks.chunkHash.tap(PLUGIN_NAME, createChunkHashHandler(PLUGIN_NAME));
  308. });
  309. }
  310. }
  311. module.exports = ModuleChunkFormatPlugin;