ModuleChunkLoadingRuntimeModule.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { SyncWaterfallHook } = require("tapable");
  6. const Compilation = require("../Compilation");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const RuntimeModule = require("../RuntimeModule");
  9. const Template = require("../Template");
  10. const {
  11. generateJavascriptHMR
  12. } = require("../hmr/JavascriptHotModuleReplacementHelper");
  13. const {
  14. chunkHasJs,
  15. getChunkFilenameTemplate
  16. } = require("../javascript/JavascriptModulesPlugin");
  17. const { getInitialChunkIds } = require("../javascript/StartupHelpers");
  18. const compileBooleanMatcher = require("../util/compileBooleanMatcher");
  19. const { getUndoPath } = require("../util/identifier");
  20. /** @typedef {import("../Chunk")} Chunk */
  21. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  22. /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
  23. /**
  24. * Defines the jsonp compilation plugin hooks type used by this module.
  25. * @typedef {object} JsonpCompilationPluginHooks
  26. * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
  27. * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
  28. */
  29. /** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
  30. const compilationHooksMap = new WeakMap();
  31. class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
  32. /**
  33. * Returns hooks.
  34. * @param {Compilation} compilation the compilation
  35. * @returns {JsonpCompilationPluginHooks} hooks
  36. */
  37. static getCompilationHooks(compilation) {
  38. if (!(compilation instanceof Compilation)) {
  39. throw new TypeError(
  40. "The 'compilation' argument must be an instance of Compilation"
  41. );
  42. }
  43. let hooks = compilationHooksMap.get(compilation);
  44. if (hooks === undefined) {
  45. hooks = {
  46. linkPreload: new SyncWaterfallHook(["source", "chunk"]),
  47. linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
  48. };
  49. compilationHooksMap.set(compilation, hooks);
  50. }
  51. return hooks;
  52. }
  53. /**
  54. * Creates an instance of ModuleChunkLoadingRuntimeModule.
  55. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  56. */
  57. constructor(runtimeRequirements) {
  58. super("import chunk loading", RuntimeModule.STAGE_ATTACH);
  59. /** @type {ReadOnlyRuntimeRequirements} */
  60. this._runtimeRequirements = runtimeRequirements;
  61. }
  62. /**
  63. * Returns generated code.
  64. * @private
  65. * @param {Chunk} chunk chunk
  66. * @param {string} rootOutputDir root output directory
  67. * @returns {string} generated code
  68. */
  69. _generateBaseUri(chunk, rootOutputDir) {
  70. const options = chunk.getEntryOptions();
  71. if (options && options.baseUri) {
  72. return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`;
  73. }
  74. const compilation = /** @type {Compilation} */ (this.compilation);
  75. const {
  76. outputOptions: { importMetaName }
  77. } = compilation;
  78. return `${RuntimeGlobals.baseURI} = new URL(${JSON.stringify(
  79. rootOutputDir
  80. )}, ${importMetaName}.url);`;
  81. }
  82. /**
  83. * Generates runtime code for this runtime module.
  84. * @returns {string | null} runtime code
  85. */
  86. generate() {
  87. const compilation = /** @type {Compilation} */ (this.compilation);
  88. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  89. const chunk = /** @type {Chunk} */ (this.chunk);
  90. const environment = compilation.outputOptions.environment;
  91. const {
  92. runtimeTemplate,
  93. outputOptions: { importFunctionName, crossOriginLoading, charset }
  94. } = compilation;
  95. const fn = RuntimeGlobals.ensureChunkHandlers;
  96. const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
  97. const withExternalInstallChunk = this._runtimeRequirements.has(
  98. RuntimeGlobals.externalInstallChunk
  99. );
  100. const withLoading = this._runtimeRequirements.has(
  101. RuntimeGlobals.ensureChunkHandlers
  102. );
  103. const withOnChunkLoad = this._runtimeRequirements.has(
  104. RuntimeGlobals.onChunksLoaded
  105. );
  106. const withHmr = this._runtimeRequirements.has(
  107. RuntimeGlobals.hmrDownloadUpdateHandlers
  108. );
  109. const withHmrManifest = this._runtimeRequirements.has(
  110. RuntimeGlobals.hmrDownloadManifest
  111. );
  112. const { linkPreload, linkPrefetch } =
  113. ModuleChunkLoadingRuntimeModule.getCompilationHooks(compilation);
  114. const isNeutralPlatform = runtimeTemplate.isNeutralPlatform();
  115. const withPrefetch =
  116. (environment.document || isNeutralPlatform) &&
  117. this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) &&
  118. chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs);
  119. const withPreload =
  120. (environment.document || isNeutralPlatform) &&
  121. this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) &&
  122. chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs);
  123. const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
  124. const hasJsMatcher = compileBooleanMatcher(conditionMap);
  125. const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
  126. const outputName = compilation.getPath(
  127. getChunkFilenameTemplate(chunk, compilation.outputOptions),
  128. {
  129. chunk,
  130. contentHashType: "javascript"
  131. }
  132. );
  133. const rootOutputDir = getUndoPath(
  134. outputName,
  135. compilation.outputOptions.path,
  136. true
  137. );
  138. const stateExpression = withHmr
  139. ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_module`
  140. : undefined;
  141. return Template.asString([
  142. withBaseURI
  143. ? this._generateBaseUri(chunk, rootOutputDir)
  144. : "// no baseURI",
  145. "",
  146. "// object to store loaded and loading chunks",
  147. "// undefined = chunk not loaded, null = chunk preloaded/prefetched",
  148. "// [resolve, Promise] = chunk loading, 0 = chunk loaded",
  149. `var installedChunks = ${
  150. stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
  151. }{`,
  152. Template.indent(
  153. Array.from(initialChunkIds, (id) => `${JSON.stringify(id)}: 0`).join(
  154. ",\n"
  155. )
  156. ),
  157. "};",
  158. "",
  159. withLoading || withExternalInstallChunk
  160. ? `var installChunk = ${runtimeTemplate.basicFunction("data", [
  161. runtimeTemplate.destructureObject(
  162. [
  163. RuntimeGlobals.esmIds,
  164. RuntimeGlobals.esmModules,
  165. RuntimeGlobals.esmRuntime
  166. ],
  167. "data"
  168. ),
  169. '// add "modules" to the modules object,',
  170. '// then flag all "ids" as loaded and fire callback',
  171. "var moduleId, chunkId, i = 0;",
  172. `for(moduleId in ${RuntimeGlobals.esmModules}) {`,
  173. Template.indent([
  174. `if(${RuntimeGlobals.hasOwnProperty}(${RuntimeGlobals.esmModules}, moduleId)) {`,
  175. Template.indent(
  176. `${RuntimeGlobals.moduleFactories}[moduleId] = ${RuntimeGlobals.esmModules}[moduleId];`
  177. ),
  178. "}"
  179. ]),
  180. "}",
  181. `if(${RuntimeGlobals.esmRuntime}) ${RuntimeGlobals.esmRuntime}(${RuntimeGlobals.require});`,
  182. `for(;i < ${RuntimeGlobals.esmIds}.length; i++) {`,
  183. Template.indent([
  184. `chunkId = ${RuntimeGlobals.esmIds}[i];`,
  185. `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
  186. Template.indent("installedChunks[chunkId][0]();"),
  187. "}",
  188. `installedChunks[${RuntimeGlobals.esmIds}[i]] = 0;`
  189. ]),
  190. "}",
  191. withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
  192. ])}`
  193. : "// no install chunk",
  194. "",
  195. withLoading
  196. ? Template.asString([
  197. `${fn}.j = ${runtimeTemplate.basicFunction(
  198. "chunkId, promises",
  199. hasJsMatcher !== false
  200. ? Template.indent([
  201. "// import() chunk loading for javascript",
  202. `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
  203. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  204. Template.indent([
  205. "",
  206. '// a Promise means "currently loading".',
  207. "if(installedChunkData) {",
  208. Template.indent([
  209. "promises.push(installedChunkData[1]);"
  210. ]),
  211. "} else {",
  212. Template.indent([
  213. hasJsMatcher === true
  214. ? "if(true) { // all chunks have JS"
  215. : `if(${hasJsMatcher("chunkId")}) {`,
  216. Template.indent([
  217. "// setup Promise in chunk cache",
  218. `var promise = ${importFunctionName}(${
  219. compilation.outputOptions.publicPath === "auto"
  220. ? JSON.stringify(rootOutputDir)
  221. : RuntimeGlobals.publicPath
  222. } + ${
  223. RuntimeGlobals.getChunkScriptFilename
  224. }(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction(
  225. "e",
  226. [
  227. "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
  228. "throw e;"
  229. ]
  230. )});`,
  231. `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction(
  232. "installedChunkData = installedChunks[chunkId] = [resolve]",
  233. "resolve"
  234. )})])`,
  235. "promises.push(installedChunkData[1] = promise);"
  236. ]),
  237. hasJsMatcher === true
  238. ? "}"
  239. : "} else installedChunks[chunkId] = 0;"
  240. ]),
  241. "}"
  242. ]),
  243. "}"
  244. ])
  245. : Template.indent(["installedChunks[chunkId] = 0;"])
  246. )};`
  247. ])
  248. : "// no chunk on demand loading",
  249. "",
  250. withPrefetch && hasJsMatcher !== false
  251. ? `${
  252. RuntimeGlobals.prefetchChunkHandlers
  253. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  254. isNeutralPlatform
  255. ? "if (typeof document === 'undefined') return;"
  256. : "",
  257. `if((!${
  258. RuntimeGlobals.hasOwnProperty
  259. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  260. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  261. }) {`,
  262. Template.indent([
  263. "installedChunks[chunkId] = null;",
  264. linkPrefetch.call(
  265. Template.asString([
  266. "var link = document.createElement('link');",
  267. charset ? "link.charset = 'utf-8';" : "",
  268. crossOriginLoading
  269. ? `link.crossOrigin = ${JSON.stringify(
  270. crossOriginLoading
  271. )};`
  272. : "",
  273. `if (${RuntimeGlobals.scriptNonce}) {`,
  274. Template.indent(
  275. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  276. ),
  277. "}",
  278. 'link.rel = "prefetch";',
  279. 'link.as = "script";',
  280. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`
  281. ]),
  282. chunk
  283. ),
  284. "document.head.appendChild(link);"
  285. ]),
  286. "}"
  287. ])};`
  288. : "// no prefetching",
  289. "",
  290. withPreload && hasJsMatcher !== false
  291. ? `${
  292. RuntimeGlobals.preloadChunkHandlers
  293. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  294. isNeutralPlatform
  295. ? "if (typeof document === 'undefined') return;"
  296. : "",
  297. `if((!${
  298. RuntimeGlobals.hasOwnProperty
  299. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  300. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  301. }) {`,
  302. Template.indent([
  303. "installedChunks[chunkId] = null;",
  304. linkPreload.call(
  305. Template.asString([
  306. "var link = document.createElement('link');",
  307. charset ? "link.charset = 'utf-8';" : "",
  308. `if (${RuntimeGlobals.scriptNonce}) {`,
  309. Template.indent(
  310. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  311. ),
  312. "}",
  313. 'link.rel = "modulepreload";',
  314. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
  315. crossOriginLoading
  316. ? crossOriginLoading === "use-credentials"
  317. ? 'link.crossOrigin = "use-credentials";'
  318. : Template.asString([
  319. "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
  320. Template.indent(
  321. `link.crossOrigin = ${JSON.stringify(
  322. crossOriginLoading
  323. )};`
  324. ),
  325. "}"
  326. ])
  327. : ""
  328. ]),
  329. chunk
  330. ),
  331. "document.head.appendChild(link);"
  332. ]),
  333. "}"
  334. ])};`
  335. : "// no preloaded",
  336. "",
  337. withExternalInstallChunk
  338. ? Template.asString([
  339. `${RuntimeGlobals.externalInstallChunk} = installChunk;`
  340. ])
  341. : "// no external install chunk",
  342. "",
  343. withOnChunkLoad
  344. ? `${
  345. RuntimeGlobals.onChunksLoaded
  346. }.j = ${runtimeTemplate.returningFunction(
  347. "installedChunks[chunkId] === 0",
  348. "chunkId"
  349. )};`
  350. : "// no on chunks loaded",
  351. withHmr
  352. ? Template.asString([
  353. generateJavascriptHMR("module"),
  354. "",
  355. "function loadUpdateChunk(chunkId, updatedModulesList) {",
  356. Template.indent([
  357. `return new Promise(${runtimeTemplate.basicFunction(
  358. "resolve, reject",
  359. [
  360. "// start update chunk loading",
  361. `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`,
  362. `var onResolve = ${runtimeTemplate.basicFunction("obj", [
  363. `var updatedModules = obj.${RuntimeGlobals.esmModules};`,
  364. `var updatedRuntime = obj.${RuntimeGlobals.esmRuntime};`,
  365. "if(updatedRuntime) currentUpdateRuntime.push(updatedRuntime);",
  366. "for(var moduleId in updatedModules) {",
  367. Template.indent([
  368. `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`,
  369. Template.indent([
  370. "currentUpdate[moduleId] = updatedModules[moduleId];",
  371. "if(updatedModulesList) updatedModulesList.push(moduleId);"
  372. ]),
  373. "}"
  374. ]),
  375. "}",
  376. "resolve(obj);"
  377. ])};`,
  378. `var onReject = ${runtimeTemplate.basicFunction("error", [
  379. "var errorMsg = error.message || 'unknown reason';",
  380. "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorMsg + ')';",
  381. "error.name = 'ChunkLoadError';",
  382. "reject(error);"
  383. ])}`,
  384. `var loadScript = ${runtimeTemplate.basicFunction(
  385. "url, onResolve, onReject",
  386. [
  387. `return ${importFunctionName}(/* webpackIgnore: true */ url).then(onResolve).catch(onReject)`
  388. ]
  389. )}`,
  390. "loadScript(url, onResolve, onReject);"
  391. ]
  392. )});`
  393. ]),
  394. "}",
  395. ""
  396. ])
  397. : "// no HMR",
  398. "",
  399. withHmrManifest
  400. ? Template.asString([
  401. `${
  402. RuntimeGlobals.hmrDownloadManifest
  403. } = ${runtimeTemplate.basicFunction("", [
  404. `return ${importFunctionName}(/* webpackIgnore: true */ ${RuntimeGlobals.publicPath} + ${
  405. RuntimeGlobals.getUpdateManifestFilename
  406. }()).then(${runtimeTemplate.basicFunction("obj", [
  407. "return obj.default;"
  408. ])}, ${runtimeTemplate.basicFunction("error", [
  409. "if(['MODULE_NOT_FOUND', 'ENOENT'].includes(error.code)) return;",
  410. "throw error;"
  411. ])});`
  412. ])};`
  413. ])
  414. : "// no HMR manifest"
  415. ]);
  416. }
  417. }
  418. module.exports = ModuleChunkLoadingRuntimeModule;