ModuleChunkFormatPlugin.js 7.9 KB

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