ModuleChunkFormatPlugin.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 { RuntimeGlobals } = require("..");
  8. const HotUpdateChunk = require("../HotUpdateChunk");
  9. const Template = require("../Template");
  10. const { getAllChunks } = require("../javascript/ChunkHelpers");
  11. const {
  12. chunkHasJs,
  13. getCompilationHooks,
  14. getChunkFilenameTemplate
  15. } = require("../javascript/JavascriptModulesPlugin");
  16. const { updateHashForEntryStartup } = require("../javascript/StartupHelpers");
  17. const { getUndoPath } = require("../util/identifier");
  18. /** @typedef {import("../Chunk")} Chunk */
  19. /** @typedef {import("../Compiler")} Compiler */
  20. /** @typedef {import("../Entrypoint")} Entrypoint */
  21. class ModuleChunkFormatPlugin {
  22. /**
  23. * Apply the plugin
  24. * @param {Compiler} compiler the compiler instance
  25. * @returns {void}
  26. */
  27. apply(compiler) {
  28. compiler.hooks.thisCompilation.tap(
  29. "ModuleChunkFormatPlugin",
  30. compilation => {
  31. compilation.hooks.additionalChunkRuntimeRequirements.tap(
  32. "ModuleChunkFormatPlugin",
  33. (chunk, set) => {
  34. if (chunk.hasRuntime()) return;
  35. if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
  36. set.add(RuntimeGlobals.require);
  37. set.add(RuntimeGlobals.startupEntrypoint);
  38. set.add(RuntimeGlobals.externalInstallChunk);
  39. }
  40. }
  41. );
  42. const hooks = getCompilationHooks(compilation);
  43. hooks.renderChunk.tap(
  44. "ModuleChunkFormatPlugin",
  45. (modules, renderContext) => {
  46. const { chunk, chunkGraph, runtimeTemplate } = renderContext;
  47. const hotUpdateChunk =
  48. chunk instanceof HotUpdateChunk ? chunk : null;
  49. const source = new ConcatSource();
  50. if (hotUpdateChunk) {
  51. throw new Error(
  52. "HMR is not implemented for module chunk format yet"
  53. );
  54. } else {
  55. source.add(
  56. `export const __webpack_id__ = ${JSON.stringify(chunk.id)};\n`
  57. );
  58. source.add(
  59. `export const __webpack_ids__ = ${JSON.stringify(chunk.ids)};\n`
  60. );
  61. source.add("export const __webpack_modules__ = ");
  62. source.add(modules);
  63. source.add(";\n");
  64. const runtimeModules =
  65. chunkGraph.getChunkRuntimeModulesInOrder(chunk);
  66. if (runtimeModules.length > 0) {
  67. source.add("export const __webpack_runtime__ =\n");
  68. source.add(
  69. Template.renderChunkRuntimeModules(
  70. runtimeModules,
  71. renderContext
  72. )
  73. );
  74. }
  75. const entries = Array.from(
  76. chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
  77. );
  78. if (entries.length > 0) {
  79. const runtimeChunk =
  80. /** @type {Entrypoint[][]} */
  81. (entries)[0][1].getRuntimeChunk();
  82. const currentOutputName = compilation
  83. .getPath(
  84. getChunkFilenameTemplate(chunk, compilation.outputOptions),
  85. {
  86. chunk,
  87. contentHashType: "javascript"
  88. }
  89. )
  90. .replace(/^\/+/g, "")
  91. .split("/");
  92. /**
  93. * @param {Chunk} chunk the chunk
  94. * @returns {string} the relative path
  95. */
  96. const getRelativePath = chunk => {
  97. const baseOutputName = currentOutputName.slice();
  98. const chunkOutputName = compilation
  99. .getPath(
  100. getChunkFilenameTemplate(
  101. chunk,
  102. compilation.outputOptions
  103. ),
  104. {
  105. chunk,
  106. contentHashType: "javascript"
  107. }
  108. )
  109. .replace(/^\/+/g, "")
  110. .split("/");
  111. // remove common parts except filename
  112. while (
  113. baseOutputName.length > 1 &&
  114. chunkOutputName.length > 1 &&
  115. baseOutputName[0] === chunkOutputName[0]
  116. ) {
  117. baseOutputName.shift();
  118. chunkOutputName.shift();
  119. }
  120. const last = chunkOutputName.join("/");
  121. // create final path
  122. return (
  123. getUndoPath(baseOutputName.join("/"), last, true) + last
  124. );
  125. };
  126. const entrySource = new ConcatSource();
  127. entrySource.add(source);
  128. entrySource.add(";\n\n// load runtime\n");
  129. entrySource.add(
  130. `import ${RuntimeGlobals.require} from ${JSON.stringify(
  131. getRelativePath(/** @type {Chunk} */ (runtimeChunk))
  132. )};\n`
  133. );
  134. const startupSource = new ConcatSource();
  135. startupSource.add(
  136. `var __webpack_exec__ = ${runtimeTemplate.returningFunction(
  137. `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`,
  138. "moduleId"
  139. )}\n`
  140. );
  141. const loadedChunks = new Set();
  142. let index = 0;
  143. for (let i = 0; i < entries.length; i++) {
  144. const [module, entrypoint] = entries[i];
  145. const final = i + 1 === entries.length;
  146. const moduleId = chunkGraph.getModuleId(module);
  147. const chunks = getAllChunks(
  148. /** @type {Entrypoint} */ (entrypoint),
  149. /** @type {Chunk} */ (runtimeChunk),
  150. undefined
  151. );
  152. for (const chunk of chunks) {
  153. if (
  154. loadedChunks.has(chunk) ||
  155. !chunkHasJs(chunk, chunkGraph)
  156. )
  157. continue;
  158. loadedChunks.add(chunk);
  159. startupSource.add(
  160. `import * as __webpack_chunk_${index}__ from ${JSON.stringify(
  161. getRelativePath(chunk)
  162. )};\n`
  163. );
  164. startupSource.add(
  165. `${RuntimeGlobals.externalInstallChunk}(__webpack_chunk_${index}__);\n`
  166. );
  167. index++;
  168. }
  169. startupSource.add(
  170. `${
  171. final ? `var ${RuntimeGlobals.exports} = ` : ""
  172. }__webpack_exec__(${JSON.stringify(moduleId)});\n`
  173. );
  174. }
  175. entrySource.add(
  176. hooks.renderStartup.call(
  177. startupSource,
  178. entries[entries.length - 1][0],
  179. {
  180. ...renderContext,
  181. inlined: false
  182. }
  183. )
  184. );
  185. return entrySource;
  186. }
  187. }
  188. return source;
  189. }
  190. );
  191. hooks.chunkHash.tap(
  192. "ModuleChunkFormatPlugin",
  193. (chunk, hash, { chunkGraph, runtimeTemplate }) => {
  194. if (chunk.hasRuntime()) return;
  195. hash.update("ModuleChunkFormatPlugin");
  196. hash.update("1");
  197. const entries = Array.from(
  198. chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
  199. );
  200. updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
  201. }
  202. );
  203. }
  204. );
  205. }
  206. }
  207. module.exports = ModuleChunkFormatPlugin;