JsonpChunkLoadingRuntimeModule.js 16 KB

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