ReadFileChunkLoadingRuntimeModule.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. generateJavascriptHMR
  10. } = require("../hmr/JavascriptHotModuleReplacementHelper");
  11. const { chunkHasJs } = require("../javascript/JavascriptModulesPlugin");
  12. const { getInitialChunkIds } = require("../javascript/StartupHelpers");
  13. const { renderBaseUri } = require("../runtime/baseUri");
  14. const compileBooleanMatcher = require("../util/compileBooleanMatcher");
  15. /** @import Chunk from "../Chunk" */
  16. /** @import ChunkGraph from "../ChunkGraph" */
  17. /** @import Compilation from "../Compilation" */
  18. /** @import RuntimeTemplate from "../RuntimeTemplate" */
  19. /** @import { ReadOnlyRuntimeRequirements } from "../Module" */
  20. class ReadFileChunkLoadingRuntimeModule extends RuntimeModule {
  21. /**
  22. * Creates an instance of ReadFileChunkLoadingRuntimeModule.
  23. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  24. */
  25. constructor(runtimeRequirements) {
  26. super("readFile chunk loading", RuntimeModule.STAGE_ATTACH);
  27. /** @type {ReadOnlyRuntimeRequirements} */
  28. this.runtimeRequirements = runtimeRequirements;
  29. }
  30. /**
  31. * Returns generated code.
  32. * @private
  33. * @param {Chunk} chunk chunk
  34. * @param {string} rootOutputDir root output directory
  35. * @param {RuntimeTemplate} runtimeTemplate the runtime template
  36. * @returns {string} generated code
  37. */
  38. _generateBaseUri(chunk, rootOutputDir, runtimeTemplate) {
  39. const options = chunk.getEntryOptions();
  40. return renderBaseUri(
  41. options ? options.baseUri : undefined,
  42. `require(${runtimeTemplate.renderNodePrefixForCoreModule("url")}).pathToFileURL(${
  43. rootOutputDir
  44. ? `__dirname + ${JSON.stringify(`/${rootOutputDir}`)}`
  45. : "__filename"
  46. })`
  47. );
  48. }
  49. /**
  50. * Generates runtime code for this runtime module.
  51. * @returns {string | null} runtime code
  52. */
  53. generate() {
  54. const compilation = /** @type {Compilation} */ (this.compilation);
  55. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  56. const chunk = /** @type {Chunk} */ (this.chunk);
  57. const { runtimeTemplate } = compilation;
  58. const fn = RuntimeGlobals.ensureChunkHandlers;
  59. const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI);
  60. const withExternalInstallChunk = this.runtimeRequirements.has(
  61. RuntimeGlobals.externalInstallChunk
  62. );
  63. const withOnChunkLoad = this.runtimeRequirements.has(
  64. RuntimeGlobals.onChunksLoaded
  65. );
  66. const withLoading = this.runtimeRequirements.has(
  67. RuntimeGlobals.ensureChunkHandlers
  68. );
  69. const withHmr = this.runtimeRequirements.has(
  70. RuntimeGlobals.hmrDownloadUpdateHandlers
  71. );
  72. const withHmrManifest = this.runtimeRequirements.has(
  73. RuntimeGlobals.hmrDownloadManifest
  74. );
  75. const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
  76. const hasJsMatcher = compileBooleanMatcher(conditionMap);
  77. const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
  78. const rootOutputDir = runtimeTemplate.chunkRootOutputDir(chunk, false);
  79. const stateExpression = withHmr
  80. ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_readFileVm`
  81. : undefined;
  82. const installedChunksObject = `{\n${Template.indent(
  83. Array.from(initialChunkIds, (id) => `${JSON.stringify(id)}: 0`).join(
  84. ",\n"
  85. )
  86. )}\n}`;
  87. // Every part below that reads the table. A chunk asking only for `.b` gets this
  88. // module for the base uri alone, and then has nothing to look up.
  89. const withInstalledChunks =
  90. withOnChunkLoad || withLoading || withExternalInstallChunk || withHmr;
  91. return Template.asString([
  92. withBaseURI
  93. ? this._generateBaseUri(chunk, rootOutputDir, runtimeTemplate)
  94. : "// no baseURI",
  95. "",
  96. withInstalledChunks
  97. ? Template.asString([
  98. "// object to store loaded chunks",
  99. '// "0" means "already loaded", Promise means loading',
  100. `var installedChunks = ${
  101. stateExpression
  102. ? runtimeTemplate.assignOr(
  103. stateExpression,
  104. installedChunksObject
  105. )
  106. : installedChunksObject
  107. };`
  108. ])
  109. : "// no installed chunks",
  110. "",
  111. withOnChunkLoad
  112. ? `${
  113. RuntimeGlobals.onChunksLoaded
  114. }.readFileVm = ${runtimeTemplate.returningFunction(
  115. "installedChunks[chunkId] === 0",
  116. "chunkId"
  117. )};`
  118. : "// no on chunks loaded",
  119. "",
  120. withLoading || withExternalInstallChunk
  121. ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [
  122. "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;",
  123. "for(var moduleId in moreModules) {",
  124. Template.indent([
  125. `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
  126. Template.indent([
  127. `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];`
  128. ]),
  129. "}"
  130. ]),
  131. "}",
  132. `if(runtime) runtime(${RuntimeGlobals.require});`,
  133. "for(var i = 0; i < chunkIds.length; i++) {",
  134. Template.indent([
  135. "if(installedChunks[chunkIds[i]]) {",
  136. Template.indent(["installedChunks[chunkIds[i]][0]();"]),
  137. "}",
  138. "installedChunks[chunkIds[i]] = 0;"
  139. ]),
  140. "}",
  141. withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
  142. ])};`
  143. : "// no chunk install function needed",
  144. "",
  145. withLoading
  146. ? Template.asString([
  147. "// ReadFile + VM.run chunk loading for javascript",
  148. `${fn}.readFileVm = function(chunkId, promises) {`,
  149. hasJsMatcher !== false
  150. ? Template.indent([
  151. "",
  152. "var installedChunkData = installedChunks[chunkId];",
  153. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  154. Template.indent([
  155. '// array of [resolve, reject, promise] means "currently loading"',
  156. "if(installedChunkData) {",
  157. Template.indent(["promises.push(installedChunkData[2]);"]),
  158. "} else {",
  159. Template.indent([
  160. hasJsMatcher === true
  161. ? "if(true) { // all chunks have JS"
  162. : `if(${hasJsMatcher("chunkId")}) {`,
  163. Template.indent([
  164. "// load the chunk and return promise to it",
  165. "var promise = new Promise(function(resolve, reject) {",
  166. Template.indent([
  167. "installedChunkData = installedChunks[chunkId] = [resolve, reject];",
  168. `var filename = require(${runtimeTemplate.renderNodePrefixForCoreModule("path")}).join(__dirname, ${JSON.stringify(
  169. rootOutputDir
  170. )} + ${
  171. RuntimeGlobals.getChunkScriptFilename
  172. }(chunkId));`,
  173. `require(${runtimeTemplate.renderNodePrefixForCoreModule("fs")}).readFile(filename, 'utf-8', function(err, content) {`,
  174. Template.indent([
  175. "if(err) return reject(err);",
  176. "var chunk = {};",
  177. `require(${runtimeTemplate.renderNodePrefixForCoreModule("vm")}).runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)` +
  178. `(chunk, require, require(${runtimeTemplate.renderNodePrefixForCoreModule("path")}).dirname(filename), filename);`,
  179. "installChunk(chunk);"
  180. ]),
  181. "});"
  182. ]),
  183. "});",
  184. "promises.push(installedChunkData[2] = promise);"
  185. ]),
  186. hasJsMatcher === true
  187. ? "}"
  188. : "} else installedChunks[chunkId] = 0;"
  189. ]),
  190. "}"
  191. ]),
  192. "}"
  193. ])
  194. : Template.indent(["installedChunks[chunkId] = 0;"]),
  195. "};"
  196. ])
  197. : "// no chunk loading",
  198. "",
  199. withExternalInstallChunk
  200. ? Template.asString([
  201. `module.exports = ${RuntimeGlobals.require};`,
  202. `${RuntimeGlobals.externalInstallChunk} = installChunk;`
  203. ])
  204. : "// no external install chunk",
  205. "",
  206. withHmr
  207. ? Template.asString([
  208. "function loadUpdateChunk(chunkId, updatedModulesList) {",
  209. Template.indent([
  210. "return new Promise(function(resolve, reject) {",
  211. Template.indent([
  212. `var filename = require(${runtimeTemplate.renderNodePrefixForCoreModule("path")}).join(__dirname, ${JSON.stringify(
  213. rootOutputDir
  214. )} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId));`,
  215. `require(${runtimeTemplate.renderNodePrefixForCoreModule("fs")}).readFile(filename, 'utf-8', function(err, content) {`,
  216. Template.indent([
  217. "if(err) return reject(err);",
  218. "var update = {};",
  219. `require(${runtimeTemplate.renderNodePrefixForCoreModule("vm")}).runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)` +
  220. `(update, require, require(${runtimeTemplate.renderNodePrefixForCoreModule("path")}).dirname(filename), filename);`,
  221. "var updatedModules = update.modules;",
  222. "var runtime = update.runtime;",
  223. "for(var moduleId in updatedModules) {",
  224. Template.indent([
  225. `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`,
  226. Template.indent([
  227. "currentUpdate[moduleId] = updatedModules[moduleId];",
  228. `${runtimeTemplate.optionalChaining("updatedModulesList", "push(moduleId)")};`
  229. ]),
  230. "}"
  231. ]),
  232. "}",
  233. "if(runtime) currentUpdateRuntime.push(runtime);",
  234. "resolve();"
  235. ]),
  236. "});"
  237. ]),
  238. "});"
  239. ]),
  240. "}",
  241. "",
  242. generateJavascriptHMR("readFileVm")
  243. ])
  244. : "// no HMR",
  245. "",
  246. withHmrManifest
  247. ? Template.asString([
  248. `${RuntimeGlobals.hmrDownloadManifest} = function() {`,
  249. Template.indent([
  250. "return new Promise(function(resolve, reject) {",
  251. Template.indent([
  252. `var filename = require(${runtimeTemplate.renderNodePrefixForCoreModule("path")}).join(__dirname, ${JSON.stringify(
  253. rootOutputDir
  254. )} + ${RuntimeGlobals.getUpdateManifestFilename}());`,
  255. `require(${runtimeTemplate.renderNodePrefixForCoreModule("fs")}).readFile(filename, 'utf-8', function(err, content) {`,
  256. Template.indent([
  257. "if(err) {",
  258. Template.indent([
  259. 'if(["MODULE_NOT_FOUND", "ENOENT"].includes(err.code)) return resolve();',
  260. "return reject(err);"
  261. ]),
  262. "}",
  263. "try { resolve(JSON.parse(content)); }",
  264. "catch(e) { reject(e); }"
  265. ]),
  266. "});"
  267. ]),
  268. "});"
  269. ]),
  270. "}"
  271. ])
  272. : "// no HMR manifest"
  273. ]);
  274. }
  275. }
  276. module.exports = ReadFileChunkLoadingRuntimeModule;