ModuleChunkLoadingRuntimeModule.js 14 KB

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