ModuleChunkFormatPlugin.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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 { getUndoPath } = require("../util/identifier");
  21. /** @typedef {import("webpack-sources").Source} Source */
  22. /** @typedef {import("../Chunk")} Chunk */
  23. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  24. /** @typedef {import("../Compilation")} Compilation */
  25. /** @typedef {import("../Compiler")} Compiler */
  26. /** @typedef {import("../Entrypoint")} Entrypoint */
  27. /**
  28. * Gets relative path.
  29. * @param {Compilation} compilation the compilation instance
  30. * @param {Chunk} chunk the chunk
  31. * @param {Chunk} runtimeChunk the runtime chunk
  32. * @returns {string} the relative path
  33. */
  34. const getRelativePath = (compilation, chunk, runtimeChunk) => {
  35. const currentOutputName = compilation
  36. .getPath(
  37. getChunkFilenameTemplate(runtimeChunk, compilation.outputOptions),
  38. {
  39. chunk: runtimeChunk,
  40. contentHashType: "javascript"
  41. }
  42. )
  43. .replace(/^\/+/g, "")
  44. .split("/");
  45. const baseOutputName = [...currentOutputName];
  46. const chunkOutputName = compilation
  47. .getPath(getChunkFilenameTemplate(chunk, compilation.outputOptions), {
  48. chunk,
  49. contentHashType: "javascript"
  50. })
  51. .replace(/^\/+/g, "")
  52. .split("/");
  53. // remove common parts except filename
  54. while (
  55. baseOutputName.length > 1 &&
  56. chunkOutputName.length > 1 &&
  57. baseOutputName[0] === chunkOutputName[0]
  58. ) {
  59. baseOutputName.shift();
  60. chunkOutputName.shift();
  61. }
  62. const last = chunkOutputName.join("/");
  63. // create final path
  64. return getUndoPath(baseOutputName.join("/"), last, true) + last;
  65. };
  66. /**
  67. * Renders chunk import.
  68. * @param {Compilation} compilation the compilation instance
  69. * @param {Chunk} chunk the chunk to render the import for
  70. * @param {string=} namedImport the named import to use for the import
  71. * @param {Chunk=} runtimeChunk the runtime chunk
  72. * @returns {string} the import source
  73. */
  74. function renderChunkImport(compilation, chunk, namedImport, runtimeChunk) {
  75. return `import ${namedImport ? `* as ${namedImport}` : `{ ${RuntimeGlobals.require} }`} from ${JSON.stringify(
  76. getRelativePath(compilation, chunk, runtimeChunk || chunk)
  77. )};\n`;
  78. }
  79. /**
  80. * Gets chunk named import.
  81. * @param {number} index the index of the chunk
  82. * @returns {string} the named import to use for the import
  83. */
  84. function getChunkNamedImport(index) {
  85. return `__webpack_chunk_${index}__`;
  86. }
  87. const PLUGIN_NAME = "ModuleChunkFormatPlugin";
  88. class ModuleChunkFormatPlugin {
  89. /**
  90. * Applies the plugin by registering its hooks on the compiler.
  91. * @param {Compiler} compiler the compiler instance
  92. * @returns {void}
  93. */
  94. apply(compiler) {
  95. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  96. compilation.hooks.additionalChunkRuntimeRequirements.tap(
  97. PLUGIN_NAME,
  98. (chunk, set) => {
  99. if (chunk.hasRuntime()) return;
  100. if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
  101. set.add(RuntimeGlobals.require);
  102. set.add(RuntimeGlobals.externalInstallChunk);
  103. }
  104. }
  105. );
  106. const hooks = getCompilationHooks(compilation);
  107. /**
  108. * With dependent chunks.
  109. * @param {Iterable<Chunk>} chunks the chunks to render
  110. * @param {ChunkGraph} chunkGraph the chunk graph
  111. * @param {Chunk=} runtimeChunk the runtime chunk
  112. * @returns {Source | undefined} the source
  113. */
  114. const withDependentChunks = (chunks, chunkGraph, runtimeChunk) => {
  115. if (/** @type {Set<Chunk>} */ (chunks).size > 0) {
  116. const source = new ConcatSource();
  117. let index = 0;
  118. for (const chunk of chunks) {
  119. index++;
  120. if (!chunkHasJs(chunk, chunkGraph)) {
  121. continue;
  122. }
  123. const namedImport = getChunkNamedImport(index);
  124. source.add(
  125. renderChunkImport(
  126. compilation,
  127. chunk,
  128. namedImport,
  129. runtimeChunk || chunk
  130. )
  131. );
  132. source.add(
  133. `${RuntimeGlobals.externalInstallChunk}(${namedImport});\n`
  134. );
  135. }
  136. return source;
  137. }
  138. };
  139. hooks.renderStartup.tap(
  140. PLUGIN_NAME,
  141. (modules, _lastModule, renderContext) => {
  142. const { chunk, chunkGraph } = renderContext;
  143. if (
  144. chunkGraph.getNumberOfEntryModules(chunk) > 0 &&
  145. chunk.hasRuntime()
  146. ) {
  147. const entryDependentChunks =
  148. chunkGraph.getChunkEntryDependentChunksIterable(chunk);
  149. const sourceWithDependentChunks = withDependentChunks(
  150. entryDependentChunks,
  151. chunkGraph,
  152. chunk
  153. );
  154. if (!sourceWithDependentChunks) {
  155. return modules;
  156. }
  157. if (modules.size() === 0) {
  158. return sourceWithDependentChunks;
  159. }
  160. const source = new ConcatSource();
  161. source.add(sourceWithDependentChunks);
  162. source.add("\n");
  163. source.add(modules);
  164. return source;
  165. }
  166. return modules;
  167. }
  168. );
  169. hooks.renderChunk.tap(PLUGIN_NAME, (modules, renderContext) => {
  170. const { chunk, chunkGraph, runtimeTemplate } = renderContext;
  171. const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null;
  172. const source = new ConcatSource();
  173. source.add(
  174. `export const ${RuntimeGlobals.esmId} = ${JSON.stringify(chunk.id)};\n`
  175. );
  176. source.add(
  177. `export const ${RuntimeGlobals.esmIds} = ${JSON.stringify(chunk.ids)};\n`
  178. );
  179. source.add(`export const ${RuntimeGlobals.esmModules} = `);
  180. source.add(modules);
  181. source.add(";\n");
  182. const runtimeModules = chunkGraph.getChunkRuntimeModulesInOrder(chunk);
  183. if (runtimeModules.length > 0) {
  184. source.add(`export const ${RuntimeGlobals.esmRuntime} =\n`);
  185. source.add(
  186. Template.renderChunkRuntimeModules(runtimeModules, renderContext)
  187. );
  188. }
  189. if (hotUpdateChunk) {
  190. return source;
  191. }
  192. const { entries, runtimeChunk } = getChunkInfo(chunk, chunkGraph);
  193. if (runtimeChunk) {
  194. const entrySource = new ConcatSource();
  195. entrySource.add(source);
  196. entrySource.add(";\n\n// load runtime\n");
  197. entrySource.add(
  198. renderChunkImport(compilation, runtimeChunk, "", chunk)
  199. );
  200. const startupSource = new ConcatSource();
  201. startupSource.add(
  202. `var __webpack_exec__ = ${runtimeTemplate.returningFunction(
  203. `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`,
  204. "moduleId"
  205. )}\n`
  206. );
  207. /** @type {Set<Chunk>} */
  208. const loadedChunks = new Set();
  209. for (let i = 0; i < entries.length; i++) {
  210. const [module, entrypoint] = entries[i];
  211. if (!chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
  212. continue;
  213. }
  214. const final = i + 1 === entries.length;
  215. const moduleId = chunkGraph.getModuleId(module);
  216. const chunks = getAllChunks(
  217. /** @type {Entrypoint} */ (entrypoint),
  218. /** @type {Chunk} */ (runtimeChunk),
  219. undefined
  220. );
  221. /** @type {Set<Chunk>} */
  222. const processChunks = new Set();
  223. for (const chunk of chunks) {
  224. if (loadedChunks.has(chunk)) {
  225. continue;
  226. }
  227. loadedChunks.add(chunk);
  228. processChunks.add(chunk);
  229. }
  230. const sourceWithDependentChunks = withDependentChunks(
  231. processChunks,
  232. chunkGraph,
  233. chunk
  234. );
  235. if (sourceWithDependentChunks) {
  236. startupSource.add("\n");
  237. startupSource.add(sourceWithDependentChunks);
  238. }
  239. startupSource.add(
  240. `${
  241. final ? `var ${RuntimeGlobals.exports} = ` : ""
  242. }__webpack_exec__(${JSON.stringify(moduleId)});\n`
  243. );
  244. }
  245. entrySource.add(
  246. hooks.renderStartup.call(
  247. startupSource,
  248. entries[entries.length - 1][0],
  249. renderContext
  250. )
  251. );
  252. return entrySource;
  253. }
  254. return source;
  255. });
  256. hooks.chunkHash.tap(PLUGIN_NAME, createChunkHashHandler(PLUGIN_NAME));
  257. });
  258. }
  259. }
  260. module.exports = ModuleChunkFormatPlugin;