RequireChunkLoadingRuntimeModule.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const RuntimeGlobals = require("../RuntimeGlobals");
  6. const RuntimeModule = require("../RuntimeModule");
  7. const Template = require("../Template");
  8. const {
  9. chunkHasJs,
  10. getChunkFilenameTemplate
  11. } = require("../javascript/JavascriptModulesPlugin");
  12. const { getInitialChunkIds } = require("../javascript/StartupHelpers");
  13. const compileBooleanMatcher = require("../util/compileBooleanMatcher");
  14. const { getUndoPath } = require("../util/identifier");
  15. /** @typedef {import("../Chunk")} Chunk */
  16. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  17. /** @typedef {import("../Compilation")} Compilation */
  18. /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
  19. class RequireChunkLoadingRuntimeModule extends RuntimeModule {
  20. /**
  21. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  22. */
  23. constructor(runtimeRequirements) {
  24. super("require chunk loading", RuntimeModule.STAGE_ATTACH);
  25. this.runtimeRequirements = runtimeRequirements;
  26. }
  27. /**
  28. * @private
  29. * @param {Chunk} chunk chunk
  30. * @param {string} rootOutputDir root output directory
  31. * @returns {string} generated code
  32. */
  33. _generateBaseUri(chunk, rootOutputDir) {
  34. const options = chunk.getEntryOptions();
  35. if (options && options.baseUri) {
  36. return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`;
  37. }
  38. return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${
  39. rootOutputDir !== "./"
  40. ? `__dirname + ${JSON.stringify("/" + rootOutputDir)}`
  41. : "__filename"
  42. });`;
  43. }
  44. /**
  45. * @returns {string | null} runtime code
  46. */
  47. generate() {
  48. const compilation = /** @type {Compilation} */ (this.compilation);
  49. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  50. const chunk = /** @type {Chunk} */ (this.chunk);
  51. const { runtimeTemplate } = compilation;
  52. const fn = RuntimeGlobals.ensureChunkHandlers;
  53. const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI);
  54. const withExternalInstallChunk = this.runtimeRequirements.has(
  55. RuntimeGlobals.externalInstallChunk
  56. );
  57. const withOnChunkLoad = this.runtimeRequirements.has(
  58. RuntimeGlobals.onChunksLoaded
  59. );
  60. const withLoading = this.runtimeRequirements.has(
  61. RuntimeGlobals.ensureChunkHandlers
  62. );
  63. const withHmr = this.runtimeRequirements.has(
  64. RuntimeGlobals.hmrDownloadUpdateHandlers
  65. );
  66. const withHmrManifest = this.runtimeRequirements.has(
  67. RuntimeGlobals.hmrDownloadManifest
  68. );
  69. const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
  70. const hasJsMatcher = compileBooleanMatcher(conditionMap);
  71. const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
  72. const outputName = compilation.getPath(
  73. getChunkFilenameTemplate(chunk, compilation.outputOptions),
  74. {
  75. chunk,
  76. contentHashType: "javascript"
  77. }
  78. );
  79. const rootOutputDir = getUndoPath(
  80. outputName,
  81. /** @type {string} */ (compilation.outputOptions.path),
  82. true
  83. );
  84. const stateExpression = withHmr
  85. ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_require`
  86. : undefined;
  87. return Template.asString([
  88. withBaseURI
  89. ? this._generateBaseUri(chunk, rootOutputDir)
  90. : "// no baseURI",
  91. "",
  92. "// object to store loaded chunks",
  93. '// "1" means "loaded", otherwise not loaded yet',
  94. `var installedChunks = ${
  95. stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
  96. }{`,
  97. Template.indent(
  98. Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join(
  99. ",\n"
  100. )
  101. ),
  102. "};",
  103. "",
  104. withOnChunkLoad
  105. ? `${
  106. RuntimeGlobals.onChunksLoaded
  107. }.require = ${runtimeTemplate.returningFunction(
  108. "installedChunks[chunkId]",
  109. "chunkId"
  110. )};`
  111. : "// no on chunks loaded",
  112. "",
  113. withLoading || withExternalInstallChunk
  114. ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [
  115. "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;",
  116. "for(var moduleId in moreModules) {",
  117. Template.indent([
  118. `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
  119. Template.indent([
  120. `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];`
  121. ]),
  122. "}"
  123. ]),
  124. "}",
  125. `if(runtime) runtime(${RuntimeGlobals.require});`,
  126. "for(var i = 0; i < chunkIds.length; i++)",
  127. Template.indent("installedChunks[chunkIds[i]] = 1;"),
  128. withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
  129. ])};`
  130. : "// no chunk install function needed",
  131. "",
  132. withLoading
  133. ? Template.asString([
  134. "// require() chunk loading for javascript",
  135. `${fn}.require = ${runtimeTemplate.basicFunction(
  136. "chunkId, promises",
  137. hasJsMatcher !== false
  138. ? [
  139. '// "1" is the signal for "already loaded"',
  140. "if(!installedChunks[chunkId]) {",
  141. Template.indent([
  142. hasJsMatcher === true
  143. ? "if(true) { // all chunks have JS"
  144. : `if(${hasJsMatcher("chunkId")}) {`,
  145. Template.indent([
  146. `installChunk(require(${JSON.stringify(
  147. rootOutputDir
  148. )} + ${
  149. RuntimeGlobals.getChunkScriptFilename
  150. }(chunkId)));`
  151. ]),
  152. "} else installedChunks[chunkId] = 1;",
  153. ""
  154. ]),
  155. "}"
  156. ]
  157. : "installedChunks[chunkId] = 1;"
  158. )};`
  159. ])
  160. : "// no chunk loading",
  161. "",
  162. withExternalInstallChunk
  163. ? Template.asString([
  164. `module.exports = ${RuntimeGlobals.require};`,
  165. `${RuntimeGlobals.externalInstallChunk} = installChunk;`
  166. ])
  167. : "// no external install chunk",
  168. "",
  169. withHmr
  170. ? Template.asString([
  171. "function loadUpdateChunk(chunkId, updatedModulesList) {",
  172. Template.indent([
  173. `var update = require(${JSON.stringify(rootOutputDir)} + ${
  174. RuntimeGlobals.getChunkUpdateScriptFilename
  175. }(chunkId));`,
  176. "var updatedModules = update.modules;",
  177. "var runtime = update.runtime;",
  178. "for(var moduleId in updatedModules) {",
  179. Template.indent([
  180. `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`,
  181. Template.indent([
  182. `currentUpdate[moduleId] = updatedModules[moduleId];`,
  183. "if(updatedModulesList) updatedModulesList.push(moduleId);"
  184. ]),
  185. "}"
  186. ]),
  187. "}",
  188. "if(runtime) currentUpdateRuntime.push(runtime);"
  189. ]),
  190. "}",
  191. "",
  192. Template.getFunctionContent(
  193. require("../hmr/JavascriptHotModuleReplacement.runtime.js")
  194. )
  195. .replace(/\$key\$/g, "require")
  196. .replace(/\$installedChunks\$/g, "installedChunks")
  197. .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk")
  198. .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache)
  199. .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories)
  200. .replace(
  201. /\$ensureChunkHandlers\$/g,
  202. RuntimeGlobals.ensureChunkHandlers
  203. )
  204. .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty)
  205. .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData)
  206. .replace(
  207. /\$hmrDownloadUpdateHandlers\$/g,
  208. RuntimeGlobals.hmrDownloadUpdateHandlers
  209. )
  210. .replace(
  211. /\$hmrInvalidateModuleHandlers\$/g,
  212. RuntimeGlobals.hmrInvalidateModuleHandlers
  213. )
  214. ])
  215. : "// no HMR",
  216. "",
  217. withHmrManifest
  218. ? Template.asString([
  219. `${RuntimeGlobals.hmrDownloadManifest} = function() {`,
  220. Template.indent([
  221. "return Promise.resolve().then(function() {",
  222. Template.indent([
  223. `return require(${JSON.stringify(rootOutputDir)} + ${
  224. RuntimeGlobals.getUpdateManifestFilename
  225. }());`
  226. ]),
  227. "})['catch'](function(err) { if(err.code !== 'MODULE_NOT_FOUND') throw err; });"
  228. ]),
  229. "}"
  230. ])
  231. : "// no HMR manifest"
  232. ]);
  233. }
  234. }
  235. module.exports = RequireChunkLoadingRuntimeModule;