ModuleChunkLoadingRuntimeModule.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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. ["__webpack_ids__", "__webpack_modules__", "__webpack_runtime__"],
  160. "data"
  161. ),
  162. '// add "modules" to the modules object,',
  163. '// then flag all "ids" as loaded and fire callback',
  164. "var moduleId, chunkId, i = 0;",
  165. "for(moduleId in __webpack_modules__) {",
  166. Template.indent([
  167. `if(${RuntimeGlobals.hasOwnProperty}(__webpack_modules__, moduleId)) {`,
  168. Template.indent(
  169. `${RuntimeGlobals.moduleFactories}[moduleId] = __webpack_modules__[moduleId];`
  170. ),
  171. "}"
  172. ]),
  173. "}",
  174. `if(__webpack_runtime__) __webpack_runtime__(${RuntimeGlobals.require});`,
  175. "for(;i < __webpack_ids__.length; i++) {",
  176. Template.indent([
  177. "chunkId = __webpack_ids__[i];",
  178. `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
  179. Template.indent("installedChunks[chunkId][0]();"),
  180. "}",
  181. "installedChunks[__webpack_ids__[i]] = 0;"
  182. ]),
  183. "}",
  184. withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
  185. ])}`
  186. : "// no install chunk",
  187. "",
  188. withLoading
  189. ? Template.asString([
  190. `${fn}.j = ${runtimeTemplate.basicFunction(
  191. "chunkId, promises",
  192. hasJsMatcher !== false
  193. ? Template.indent([
  194. "// import() chunk loading for javascript",
  195. `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
  196. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  197. Template.indent([
  198. "",
  199. '// a Promise means "currently loading".',
  200. "if(installedChunkData) {",
  201. Template.indent([
  202. "promises.push(installedChunkData[1]);"
  203. ]),
  204. "} else {",
  205. Template.indent([
  206. hasJsMatcher === true
  207. ? "if(true) { // all chunks have JS"
  208. : `if(${hasJsMatcher("chunkId")}) {`,
  209. Template.indent([
  210. "// setup Promise in chunk cache",
  211. `var promise = ${importFunctionName}(${
  212. compilation.outputOptions.publicPath === "auto"
  213. ? JSON.stringify(rootOutputDir)
  214. : RuntimeGlobals.publicPath
  215. } + ${
  216. RuntimeGlobals.getChunkScriptFilename
  217. }(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction(
  218. "e",
  219. [
  220. "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
  221. "throw e;"
  222. ]
  223. )});`,
  224. `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction(
  225. "installedChunkData = installedChunks[chunkId] = [resolve]",
  226. "resolve"
  227. )})])`,
  228. "promises.push(installedChunkData[1] = promise);"
  229. ]),
  230. hasJsMatcher === true
  231. ? "}"
  232. : "} else installedChunks[chunkId] = 0;"
  233. ]),
  234. "}"
  235. ]),
  236. "}"
  237. ])
  238. : Template.indent(["installedChunks[chunkId] = 0;"])
  239. )};`
  240. ])
  241. : "// no chunk on demand loading",
  242. "",
  243. withPrefetch && hasJsMatcher !== false
  244. ? `${
  245. RuntimeGlobals.prefetchChunkHandlers
  246. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  247. isNeutralPlatform
  248. ? "if (typeof document === 'undefined') return;"
  249. : "",
  250. `if((!${
  251. RuntimeGlobals.hasOwnProperty
  252. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  253. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  254. }) {`,
  255. Template.indent([
  256. "installedChunks[chunkId] = null;",
  257. linkPrefetch.call(
  258. Template.asString([
  259. "var link = document.createElement('link');",
  260. charset ? "link.charset = 'utf-8';" : "",
  261. crossOriginLoading
  262. ? `link.crossOrigin = ${JSON.stringify(
  263. crossOriginLoading
  264. )};`
  265. : "",
  266. `if (${RuntimeGlobals.scriptNonce}) {`,
  267. Template.indent(
  268. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  269. ),
  270. "}",
  271. 'link.rel = "prefetch";',
  272. 'link.as = "script";',
  273. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`
  274. ]),
  275. chunk
  276. ),
  277. "document.head.appendChild(link);"
  278. ]),
  279. "}"
  280. ])};`
  281. : "// no prefetching",
  282. "",
  283. withPreload && hasJsMatcher !== false
  284. ? `${
  285. RuntimeGlobals.preloadChunkHandlers
  286. }.j = ${runtimeTemplate.basicFunction("chunkId", [
  287. isNeutralPlatform
  288. ? "if (typeof document === 'undefined') return;"
  289. : "",
  290. `if((!${
  291. RuntimeGlobals.hasOwnProperty
  292. }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
  293. hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
  294. }) {`,
  295. Template.indent([
  296. "installedChunks[chunkId] = null;",
  297. linkPreload.call(
  298. Template.asString([
  299. "var link = document.createElement('link');",
  300. charset ? "link.charset = 'utf-8';" : "",
  301. `if (${RuntimeGlobals.scriptNonce}) {`,
  302. Template.indent(
  303. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  304. ),
  305. "}",
  306. 'link.rel = "modulepreload";',
  307. `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
  308. crossOriginLoading
  309. ? crossOriginLoading === "use-credentials"
  310. ? 'link.crossOrigin = "use-credentials";'
  311. : Template.asString([
  312. "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
  313. Template.indent(
  314. `link.crossOrigin = ${JSON.stringify(
  315. crossOriginLoading
  316. )};`
  317. ),
  318. "}"
  319. ])
  320. : ""
  321. ]),
  322. chunk
  323. ),
  324. "document.head.appendChild(link);"
  325. ]),
  326. "}"
  327. ])};`
  328. : "// no preloaded",
  329. "",
  330. withExternalInstallChunk
  331. ? Template.asString([
  332. `${RuntimeGlobals.externalInstallChunk} = installChunk;`
  333. ])
  334. : "// no external install chunk",
  335. "",
  336. withOnChunkLoad
  337. ? `${
  338. RuntimeGlobals.onChunksLoaded
  339. }.j = ${runtimeTemplate.returningFunction(
  340. "installedChunks[chunkId] === 0",
  341. "chunkId"
  342. )};`
  343. : "// no on chunks loaded",
  344. withHmr
  345. ? Template.asString([
  346. generateJavascriptHMR("module"),
  347. "",
  348. "function loadUpdateChunk(chunkId, updatedModulesList) {",
  349. Template.indent([
  350. `return new Promise(${runtimeTemplate.basicFunction(
  351. "resolve, reject",
  352. [
  353. "// start update chunk loading",
  354. `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`,
  355. `var onResolve = ${runtimeTemplate.basicFunction("obj", [
  356. "var updatedModules = obj.__webpack_modules__;",
  357. "var updatedRuntime = obj.__webpack_runtime__;",
  358. "if(updatedRuntime) currentUpdateRuntime.push(updatedRuntime);",
  359. "for(var moduleId in updatedModules) {",
  360. Template.indent([
  361. `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`,
  362. Template.indent([
  363. "currentUpdate[moduleId] = updatedModules[moduleId];",
  364. "if(updatedModulesList) updatedModulesList.push(moduleId);"
  365. ]),
  366. "}"
  367. ]),
  368. "}",
  369. "resolve(obj);"
  370. ])};`,
  371. `var onReject = ${runtimeTemplate.basicFunction("error", [
  372. "var errorMsg = error.message || 'unknown reason';",
  373. "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorMsg + ')';",
  374. "error.name = 'ChunkLoadError';",
  375. "reject(error);"
  376. ])}`,
  377. `var loadScript = ${runtimeTemplate.basicFunction(
  378. "url, onResolve, onReject",
  379. [
  380. `return ${importFunctionName}(/* webpackIgnore: true */ url).then(onResolve).catch(onReject)`
  381. ]
  382. )}
  383. loadScript(url, onResolve, onReject);`
  384. ]
  385. )});`
  386. ]),
  387. "}",
  388. ""
  389. ])
  390. : "// no HMR",
  391. "",
  392. withHmrManifest
  393. ? Template.asString([
  394. `${
  395. RuntimeGlobals.hmrDownloadManifest
  396. } = ${runtimeTemplate.basicFunction("", [
  397. `return ${importFunctionName}(/* webpackIgnore: true */ ${RuntimeGlobals.publicPath} + ${
  398. RuntimeGlobals.getUpdateManifestFilename
  399. }()).then(${runtimeTemplate.basicFunction("obj", [
  400. "return obj.default;"
  401. ])});`
  402. ])};`
  403. ])
  404. : "// no HMR manifest"
  405. ]);
  406. }
  407. }
  408. module.exports = ModuleChunkLoadingRuntimeModule;