ModuleChunkLoadingRuntimeModule.js 14 KB

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