ModuleChunkFormatPlugin.js 9.3 KB

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