JsonpChunkLoadingRuntimeModule.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { SyncWaterfallHook } = require("tapable");
  6. /** @import Compilation from "../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 chunkHasJs = require("../javascript/JavascriptModulesPlugin").chunkHasJs;
  14. const { getInitialChunkIds } = require("../javascript/StartupHelpers");
  15. const { renderBaseUri } = require("../runtime/baseUri");
  16. const compileBooleanMatcher = require("../util/compileBooleanMatcher");
  17. const createHooksRegistry = require("../util/createHooksRegistry");
  18. /** @import Chunk from "../Chunk" */
  19. /** @import ChunkGraph from "../ChunkGraph" */
  20. /** @import { ReadOnlyRuntimeRequirements } from "../Module" */
  21. /**
  22. * @typedef {object} JsonpCompilationPluginHooks
  23. * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
  24. * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
  25. */
  26. class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
  27. /**
  28. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  29. */
  30. constructor(runtimeRequirements) {
  31. super("jsonp chunk loading", RuntimeModule.STAGE_ATTACH);
  32. /** @type {ReadOnlyRuntimeRequirements} */
  33. this._runtimeRequirements = runtimeRequirements;
  34. }
  35. /**
  36. * @private
  37. * @param {Chunk} chunk chunk
  38. * @returns {string} generated code
  39. */
  40. _generateBaseUri(chunk) {
  41. const options = chunk.getEntryOptions();
  42. return renderBaseUri(
  43. options ? options.baseUri : undefined,
  44. "(typeof document !== 'undefined' && document.baseURI) || self.location.href"
  45. );
  46. }
  47. /**
  48. * Generates runtime code for this runtime module.
  49. * @returns {string | null} runtime code
  50. */
  51. generate() {
  52. const compilation = /** @type {Compilation} */ (this.compilation);
  53. const {
  54. runtimeTemplate,
  55. outputOptions: {
  56. chunkLoadingGlobal,
  57. hotUpdateGlobal,
  58. crossOriginLoading,
  59. scriptType,
  60. charset,
  61. resourceHints
  62. }
  63. } = compilation;
  64. const dedupePrefetch = Boolean(resourceHints && resourceHints.dedupe);
  65. const globalObject = runtimeTemplate.globalObject;
  66. const { linkPreload, linkPrefetch } =
  67. JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation);
  68. const fn = RuntimeGlobals.ensureChunkHandlers;
  69. const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
  70. const withLoading = this._runtimeRequirements.has(
  71. RuntimeGlobals.ensureChunkHandlers
  72. );
  73. const withCallback = this._runtimeRequirements.has(
  74. RuntimeGlobals.chunkCallback
  75. );
  76. const withOnChunkLoad = this._runtimeRequirements.has(
  77. RuntimeGlobals.onChunksLoaded
  78. );
  79. const withHmr = this._runtimeRequirements.has(
  80. RuntimeGlobals.hmrDownloadUpdateHandlers
  81. );
  82. const withHmrManifest = this._runtimeRequirements.has(
  83. RuntimeGlobals.hmrDownloadManifest
  84. );
  85. const withFetchPriority = this._runtimeRequirements.has(
  86. RuntimeGlobals.hasFetchPriority
  87. );
  88. const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify(
  89. chunkLoadingGlobal
  90. )}]`;
  91. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  92. const chunk = /** @type {Chunk} */ (this.chunk);
  93. const withPrefetch =
  94. this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) &&
  95. chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs);
  96. const withPreload =
  97. this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) &&
  98. chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs);
  99. const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
  100. const hasJsMatcher = compileBooleanMatcher(conditionMap);
  101. const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
  102. const stateExpression = withHmr
  103. ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_jsonp`
  104. : undefined;
  105. const cst = runtimeTemplate.renderConst();
  106. const lt = runtimeTemplate.renderLet();
  107. const installedChunksObject = `{\n${Template.indent(
  108. Array.from(initialChunkIds, (id) => `${JSON.stringify(id)}: 0`).join(
  109. ",\n"
  110. )
  111. )}\n}`;
  112. // Every part below that reads the table. A chunk asking only for `.b` gets this
  113. // module for the base uri alone, and then has nothing to look up.
  114. const withInstalledChunks =
  115. withLoading ||
  116. withCallback ||
  117. withOnChunkLoad ||
  118. withHmr ||
  119. withPrefetch ||
  120. withPreload;
  121. return Template.asString([
  122. withBaseURI ? this._generateBaseUri(chunk) : "// no baseURI",
  123. "",
  124. withInstalledChunks
  125. ? Template.asString([
  126. "// object to store loaded and loading chunks",
  127. "// undefined = chunk not loaded, null = chunk preloaded/prefetched",
  128. "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
  129. `${cst} installedChunks = ${
  130. stateExpression
  131. ? runtimeTemplate.assignOr(
  132. stateExpression,
  133. installedChunksObject
  134. )
  135. : installedChunksObject
  136. };`
  137. ])
  138. : "// no installed chunks",
  139. "",
  140. withLoading
  141. ? Template.asString([
  142. `${fn}.j = ${runtimeTemplate.basicFunction(
  143. `chunkId, promises${withFetchPriority ? ", fetchPriority" : ""}`,
  144. hasJsMatcher !== false
  145. ? Template.indent([
  146. "// JSONP chunk loading for javascript",
  147. `${lt} installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
  148. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  149. Template.indent([
  150. "",
  151. '// a Promise means "currently loading".',
  152. "if(installedChunkData) {",
  153. Template.indent([
  154. "promises.push(installedChunkData[2]);"
  155. ]),
  156. "} else {",
  157. Template.indent([
  158. hasJsMatcher === true
  159. ? "if(true) { // all chunks have JS"
  160. : `if(${hasJsMatcher("chunkId")}) {`,
  161. Template.indent([
  162. "// setup Promise in chunk cache",
  163. `${cst} promise = new Promise(${runtimeTemplate.expressionFunction(
  164. "installedChunkData = installedChunks[chunkId] = [resolve, reject]",
  165. "resolve, reject"
  166. )});`,
  167. "promises.push(installedChunkData[2] = promise);",
  168. "",
  169. "// create error before stack unwound to get useful stacktrace later",
  170. `${cst} error = new Error();`,
  171. `${cst} loadingEnded = ${runtimeTemplate.basicFunction(
  172. "event",
  173. [
  174. `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
  175. Template.indent([
  176. "installedChunkData = installedChunks[chunkId];",
  177. "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
  178. "if(installedChunkData) {",
  179. Template.indent([
  180. `${cst} errorType = event && (event.type === 'load' ? 'missing' : event.type);`,
  181. `${cst} realSrc = event && event.target && event.target.src;`,
  182. "error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
  183. "error.name = 'ChunkLoadError';",
  184. "error.type = errorType;",
  185. "error.request = realSrc;",
  186. "error.event = event;",
  187. "installedChunkData[1](error);"
  188. ]),
  189. "}"
  190. ]),
  191. "}"
  192. ]
  193. )};`,
  194. `${
  195. RuntimeGlobals.loadScript
  196. }(${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId), loadingEnded, "chunk-" + chunkId, chunkId${
  197. withFetchPriority ? ", fetchPriority" : ""
  198. });`
  199. ]),
  200. hasJsMatcher === true
  201. ? "}"
  202. : "} else installedChunks[chunkId] = 0;"
  203. ]),
  204. "}"
  205. ]),
  206. "}"
  207. ])
  208. : Template.indent(["installedChunks[chunkId] = 0;"])
  209. )};`
  210. ])
  211. : "// no chunk on demand loading",
  212. "",
  213. withPrefetch && hasJsMatcher !== false
  214. ? `${
  215. RuntimeGlobals.prefetchChunkHandlers
  216. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  217. `if((!${
  218. RuntimeGlobals.hasOwnProperty
  219. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  220. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  221. }) {`,
  222. Template.indent([
  223. "installedChunks[chunkId] = null;",
  224. linkPrefetch.call(
  225. Template.asString([
  226. `${cst} link = document.createElement('link');`,
  227. charset ? "link.charset = 'utf-8';" : "",
  228. crossOriginLoading
  229. ? `link.crossOrigin = ${JSON.stringify(
  230. crossOriginLoading
  231. )};`
  232. : "",
  233. `if (${RuntimeGlobals.scriptNonce}) {`,
  234. Template.indent(
  235. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  236. ),
  237. "}",
  238. 'link.rel = "prefetch";',
  239. 'link.as = "script";',
  240. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`
  241. ]),
  242. chunk
  243. ),
  244. dedupePrefetch
  245. ? Template.asString([
  246. // Chrome re-requests a resource when a prefetch link is added
  247. // after it was already (pre)loaded via markup; skip in that case.
  248. `${cst} links = document.getElementsByTagName("link");`,
  249. `for(${lt} i = 0; i < links.length; i++) {`,
  250. Template.indent([
  251. `${cst} l = links[i];`,
  252. 'if(l.href === link.href && (l.rel === "prefetch" || l.rel === "preload" || l.rel === "modulepreload")) return;'
  253. ]),
  254. "}"
  255. ])
  256. : "",
  257. "document.head.appendChild(link);"
  258. ]),
  259. "}"
  260. ])};`
  261. : "// no prefetching",
  262. "",
  263. withPreload && hasJsMatcher !== false
  264. ? `${
  265. RuntimeGlobals.preloadChunkHandlers
  266. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  267. `if((!${
  268. RuntimeGlobals.hasOwnProperty
  269. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  270. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  271. }) {`,
  272. Template.indent([
  273. "installedChunks[chunkId] = null;",
  274. linkPreload.call(
  275. Template.asString([
  276. `${cst} link = document.createElement('link');`,
  277. scriptType && scriptType !== "module"
  278. ? `link.type = ${JSON.stringify(scriptType)};`
  279. : "",
  280. charset ? "link.charset = 'utf-8';" : "",
  281. `if (${RuntimeGlobals.scriptNonce}) {`,
  282. Template.indent(
  283. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  284. ),
  285. "}",
  286. scriptType === "module"
  287. ? 'link.rel = "modulepreload";'
  288. : 'link.rel = "preload";',
  289. scriptType === "module" ? "" : 'link.as = "script";',
  290. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
  291. crossOriginLoading
  292. ? crossOriginLoading === "use-credentials"
  293. ? 'link.crossOrigin = "use-credentials";'
  294. : Template.asString([
  295. "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
  296. Template.indent(
  297. `link.crossOrigin = ${JSON.stringify(
  298. crossOriginLoading
  299. )};`
  300. ),
  301. "}"
  302. ])
  303. : ""
  304. ]),
  305. chunk
  306. ),
  307. "document.head.appendChild(link);"
  308. ]),
  309. "}"
  310. ])};`
  311. : "// no preloaded",
  312. "",
  313. withHmr
  314. ? Template.asString([
  315. `${lt} currentUpdatedModulesList;`,
  316. `${cst} waitingUpdateResolves = {};`,
  317. "function loadUpdateChunk(chunkId, updatedModulesList) {",
  318. Template.indent([
  319. "currentUpdatedModulesList = updatedModulesList;",
  320. `return new Promise(${runtimeTemplate.basicFunction(
  321. "resolve, reject",
  322. [
  323. "waitingUpdateResolves[chunkId] = resolve;",
  324. "// start update chunk loading",
  325. `${cst} url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`,
  326. "// create error before stack unwound to get useful stacktrace later",
  327. `${cst} error = new Error();`,
  328. `${cst} loadingEnded = ${runtimeTemplate.basicFunction(
  329. "event",
  330. [
  331. "if(waitingUpdateResolves[chunkId]) {",
  332. Template.indent([
  333. "waitingUpdateResolves[chunkId] = undefined",
  334. `${cst} errorType = event && (event.type === 'load' ? 'missing' : event.type);`,
  335. `${cst} realSrc = event && event.target && event.target.src;`,
  336. "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
  337. "error.name = 'ChunkLoadError';",
  338. "error.type = errorType;",
  339. "error.request = realSrc;",
  340. "error.event = event;",
  341. "reject(error);"
  342. ]),
  343. "}"
  344. ]
  345. )};`,
  346. `${RuntimeGlobals.loadScript}(url, loadingEnded);`
  347. ]
  348. )});`
  349. ]),
  350. "}",
  351. "",
  352. `${globalObject}[${JSON.stringify(
  353. hotUpdateGlobal
  354. )}] = ${runtimeTemplate.basicFunction(
  355. "chunkId, moreModules, runtime",
  356. [
  357. "for(var moduleId in moreModules) {",
  358. Template.indent([
  359. `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
  360. Template.indent([
  361. "currentUpdate[moduleId] = moreModules[moduleId];",
  362. `${runtimeTemplate.optionalChaining("currentUpdatedModulesList", "push(moduleId)")};`
  363. ]),
  364. "}"
  365. ]),
  366. "}",
  367. "if(runtime) currentUpdateRuntime.push(runtime);",
  368. "if(waitingUpdateResolves[chunkId]) {",
  369. Template.indent([
  370. "waitingUpdateResolves[chunkId]();",
  371. "waitingUpdateResolves[chunkId] = undefined;"
  372. ]),
  373. "}"
  374. ]
  375. )};`,
  376. "",
  377. generateJavascriptHMR("jsonp")
  378. ])
  379. : "// no HMR",
  380. "",
  381. withHmrManifest
  382. ? Template.asString([
  383. `${
  384. RuntimeGlobals.hmrDownloadManifest
  385. } = ${runtimeTemplate.basicFunction("", [
  386. 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");',
  387. `return fetch(${RuntimeGlobals.publicPath} + ${
  388. RuntimeGlobals.getUpdateManifestFilename
  389. }()).then(${runtimeTemplate.basicFunction("response", [
  390. "if(response.status === 404) return; // no update available",
  391. 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);',
  392. "return response.json();"
  393. ])});`
  394. ])};`
  395. ])
  396. : "// no HMR manifest",
  397. "",
  398. withOnChunkLoad
  399. ? `${
  400. RuntimeGlobals.onChunksLoaded
  401. }.j = ${runtimeTemplate.returningFunction(
  402. "installedChunks[chunkId] === 0",
  403. "chunkId"
  404. )};`
  405. : "// no on chunks loaded",
  406. "",
  407. withCallback || withLoading
  408. ? Template.asString([
  409. "// install a JSONP callback for chunk loading",
  410. `${cst} webpackJsonpCallback = ${runtimeTemplate.basicFunction(
  411. "parentChunkLoadingFunction, data",
  412. [
  413. runtimeTemplate.destructureArray(
  414. ["chunkIds", "moreModules", "runtime"],
  415. "data"
  416. ),
  417. '// add "moreModules" to the modules object,',
  418. '// then flag all "chunkIds" as loaded and fire callback',
  419. "var moduleId, chunkId, i = 0;",
  420. `if(chunkIds.some(${runtimeTemplate.returningFunction(
  421. "installedChunks[id] !== 0",
  422. "id"
  423. )})) {`,
  424. Template.indent([
  425. "for(moduleId in moreModules) {",
  426. Template.indent([
  427. `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
  428. Template.indent(
  429. `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];`
  430. ),
  431. "}"
  432. ]),
  433. "}",
  434. `if(runtime) var result = runtime(${RuntimeGlobals.require});`
  435. ]),
  436. "}",
  437. "if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);",
  438. "for(;i < chunkIds.length; i++) {",
  439. Template.indent([
  440. "chunkId = chunkIds[i];",
  441. `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
  442. Template.indent("installedChunks[chunkId][0]();"),
  443. "}",
  444. "installedChunks[chunkId] = 0;"
  445. ]),
  446. "}",
  447. withOnChunkLoad
  448. ? `return ${RuntimeGlobals.onChunksLoaded}(result);`
  449. : ""
  450. ]
  451. )}`,
  452. "",
  453. `${cst} chunkLoadingGlobal = ${runtimeTemplate.assignOr(chunkLoadingGlobalExpr, "[]")};`,
  454. "chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));",
  455. "chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"
  456. ])
  457. : "// no jsonp function"
  458. ]);
  459. }
  460. }
  461. JsonpChunkLoadingRuntimeModule.getCompilationHooks = createHooksRegistry(
  462. () =>
  463. /** @type {JsonpCompilationPluginHooks} */ ({
  464. linkPreload: new SyncWaterfallHook(["source", "chunk"]),
  465. linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
  466. })
  467. );
  468. module.exports = JsonpChunkLoadingRuntimeModule;