ConsumeSharedRuntimeModule.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const RuntimeModule = require("../RuntimeModule");
  8. const Template = require("../Template");
  9. const { compareModulesById } = require("../util/comparators");
  10. const {
  11. parseVersionRuntimeCode,
  12. rangeToStringRuntimeCode,
  13. satisfyRuntimeCode,
  14. versionLtRuntimeCode
  15. } = require("../util/semver");
  16. /** @import { Source } from "webpack-sources" */
  17. /** @import Chunk, { ChunkId } from "../Chunk" */
  18. /** @import ChunkGraph, { ModuleId } from "../ChunkGraph" */
  19. /** @import Compilation from "../Compilation" */
  20. /** @import Module, { ReadOnlyRuntimeRequirements } from "../Module" */
  21. /** @import CodeGenerationResults from "../CodeGenerationResults" */
  22. class ConsumeSharedRuntimeModule extends RuntimeModule {
  23. /**
  24. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  25. */
  26. constructor(runtimeRequirements) {
  27. super("consumes", RuntimeModule.STAGE_ATTACH);
  28. /** @type {ReadOnlyRuntimeRequirements} */
  29. this._runtimeRequirements = runtimeRequirements;
  30. }
  31. /**
  32. * Generates runtime code for this runtime module.
  33. * @returns {string | null} runtime code
  34. */
  35. generate() {
  36. const compilation = /** @type {Compilation} */ (this.compilation);
  37. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  38. const codeGenerationResults =
  39. /** @type {CodeGenerationResults} */
  40. (compilation.codeGenerationResults);
  41. const { runtimeTemplate } = compilation;
  42. /** @type {Record<ChunkId, ModuleId[]>} */
  43. const chunkToModuleMapping = {};
  44. /** @type {Map<ModuleId, Source>} */
  45. const moduleIdToSourceMapping = new Map();
  46. /** @type {ModuleId[]} */
  47. const initialConsumes = [];
  48. /**
  49. * @param {Iterable<Module>} modules modules
  50. * @param {Chunk} chunk the chunk
  51. * @param {ModuleId[]} list list of ids
  52. */
  53. const addModules = (modules, chunk, list) => {
  54. for (const m of modules) {
  55. const module = m;
  56. const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module));
  57. list.push(id);
  58. moduleIdToSourceMapping.set(
  59. id,
  60. codeGenerationResults.getSource(
  61. module,
  62. chunk.runtime,
  63. "consume-shared"
  64. )
  65. );
  66. }
  67. };
  68. const byId = compareModulesById(chunkGraph);
  69. for (const chunk of /** @type {Chunk} */ (
  70. this.chunk
  71. ).getAllReferencedChunks()) {
  72. const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
  73. chunk,
  74. "consume-shared",
  75. byId
  76. );
  77. if (!modules) continue;
  78. addModules(
  79. modules,
  80. chunk,
  81. (chunkToModuleMapping[/** @type {ChunkId} */ (chunk.id)] = [])
  82. );
  83. }
  84. for (const chunk of /** @type {Chunk} */ (
  85. this.chunk
  86. ).getAllInitialChunks()) {
  87. const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
  88. chunk,
  89. "consume-shared",
  90. byId
  91. );
  92. if (!modules) continue;
  93. addModules(modules, chunk, initialConsumes);
  94. }
  95. if (moduleIdToSourceMapping.size === 0) return null;
  96. const cst = runtimeTemplate.renderConst();
  97. return Template.asString([
  98. parseVersionRuntimeCode(runtimeTemplate),
  99. versionLtRuntimeCode(runtimeTemplate),
  100. rangeToStringRuntimeCode(runtimeTemplate),
  101. satisfyRuntimeCode(runtimeTemplate),
  102. `${cst} exists = ${runtimeTemplate.basicFunction("scope, key", [
  103. `return scope && ${RuntimeGlobals.hasOwnProperty}(scope, key);`
  104. ])}`,
  105. `${cst} get = ${runtimeTemplate.basicFunction("entry", [
  106. "entry.loaded = 1;",
  107. "return entry.get()"
  108. ])};`,
  109. `${cst} eagerOnly = ${runtimeTemplate.basicFunction("versions", [
  110. `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
  111. "filtered, version",
  112. Template.indent([
  113. "if (versions[version].eager) {",
  114. Template.indent(["filtered[version] = versions[version];"]),
  115. "}",
  116. "return filtered;"
  117. ])
  118. )}, {});`
  119. ])};`,
  120. `${cst} findLatestVersion = ${runtimeTemplate.basicFunction(
  121. "scope, key, eager",
  122. [
  123. `${cst} versions = eager ? eagerOnly(scope[key]) : scope[key];`,
  124. `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
  125. "a, b",
  126. ["return !a || versionLt(a, b) ? b : a;"]
  127. )}, 0);`,
  128. "return key && versions[key];"
  129. ]
  130. )};`,
  131. `${cst} findSatisfyingVersion = ${runtimeTemplate.basicFunction(
  132. "scope, key, requiredVersion, eager",
  133. [
  134. `${cst} versions = eager ? eagerOnly(scope[key]) : scope[key];`,
  135. `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
  136. "a, b",
  137. [
  138. "if (!satisfy(requiredVersion, b)) return a;",
  139. "return !a || versionLt(a, b) ? b : a;"
  140. ]
  141. )}, 0);`,
  142. "return key && versions[key]"
  143. ]
  144. )};`,
  145. `${cst} findSingletonVersionKey = ${runtimeTemplate.basicFunction(
  146. "scope, key, eager",
  147. [
  148. `${cst} versions = eager ? eagerOnly(scope[key]) : scope[key];`,
  149. `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
  150. "a, b",
  151. ["return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;"]
  152. )}, 0);`
  153. ]
  154. )};`,
  155. `${cst} getInvalidSingletonVersionMessage = ${runtimeTemplate.basicFunction(
  156. "scope, key, version, requiredVersion",
  157. [
  158. 'return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")"'
  159. ]
  160. )};`,
  161. `${cst} getInvalidVersionMessage = ${runtimeTemplate.basicFunction(
  162. "scope, scopeName, key, requiredVersion, eager",
  163. [
  164. `${cst} versions = scope[key];`,
  165. 'return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\\n" +',
  166. `\t"Available versions: " + Object.keys(versions).map(${runtimeTemplate.basicFunction(
  167. "key",
  168. ['return key + " from " + versions[key].from;']
  169. )}).join(", ");`
  170. ]
  171. )};`,
  172. `${cst} fail = ${runtimeTemplate.basicFunction("msg", [
  173. "throw new Error(msg);"
  174. ])}`,
  175. `${cst} failAsNotExist = ${runtimeTemplate.basicFunction(
  176. "scopeName, key",
  177. [
  178. 'return fail("Shared module " + key + " doesn\'t exist in shared scope " + scopeName);'
  179. ]
  180. )}`,
  181. `${cst} warn = /*#__PURE__*/ ${
  182. compilation.outputOptions.ignoreBrowserWarnings
  183. ? runtimeTemplate.basicFunction("", "")
  184. : runtimeTemplate.basicFunction("msg", [
  185. 'if (typeof console !== "undefined" && console.warn) console.warn(msg);'
  186. ])
  187. };`,
  188. `${cst} init = ${runtimeTemplate.returningFunction(
  189. Template.asString([
  190. "function(scopeName, key, eager, c, d) {",
  191. Template.indent([
  192. `${cst} promise = ${RuntimeGlobals.initializeSharing}(scopeName);`,
  193. // if we require eager shared, we expect it to be already loaded before it requested, no need to wait the whole scope loaded.
  194. `if (${runtimeTemplate.optionalChaining("promise", "then")} && !eager) { `,
  195. Template.indent([
  196. `return promise.then(fn.bind(fn, scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, false, c, d));`
  197. ]),
  198. "}",
  199. `return fn(scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, eager, c, d);`
  200. ]),
  201. "}"
  202. ]),
  203. "fn"
  204. )};`,
  205. "",
  206. `${cst} useFallback = ${runtimeTemplate.basicFunction(
  207. "scopeName, key, fallback",
  208. ["return fallback ? fallback() : failAsNotExist(scopeName, key);"]
  209. )}`,
  210. `${cst} load = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  211. "scopeName, scope, key, eager, fallback",
  212. [
  213. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  214. "return get(findLatestVersion(scope, key, eager));"
  215. ]
  216. )});`,
  217. `${cst} loadVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  218. "scopeName, scope, key, eager, requiredVersion, fallback",
  219. [
  220. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  221. `${cst} satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);`,
  222. "if (satisfyingVersion) return get(satisfyingVersion);",
  223. "warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))",
  224. "return get(findLatestVersion(scope, key, eager));"
  225. ]
  226. )});`,
  227. `${cst} loadStrictVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  228. "scopeName, scope, key, eager, requiredVersion, fallback",
  229. [
  230. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  231. `${cst} satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);`,
  232. "if (satisfyingVersion) return get(satisfyingVersion);",
  233. "if (fallback) return fallback();",
  234. "fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));"
  235. ]
  236. )});`,
  237. `${cst} loadSingleton = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  238. "scopeName, scope, key, eager, fallback",
  239. [
  240. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  241. `${cst} version = findSingletonVersionKey(scope, key, eager);`,
  242. "return get(scope[key][version]);"
  243. ]
  244. )});`,
  245. `${cst} loadSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  246. "scopeName, scope, key, eager, requiredVersion, fallback",
  247. [
  248. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  249. `${cst} version = findSingletonVersionKey(scope, key, eager);`,
  250. "if (!satisfy(requiredVersion, version)) {",
  251. Template.indent([
  252. "warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));"
  253. ]),
  254. "}",
  255. "return get(scope[key][version]);"
  256. ]
  257. )});`,
  258. `${cst} loadStrictSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
  259. "scopeName, scope, key, eager, requiredVersion, fallback",
  260. [
  261. "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
  262. `${cst} version = findSingletonVersionKey(scope, key, eager);`,
  263. "if (!satisfy(requiredVersion, version)) {",
  264. Template.indent([
  265. "fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));"
  266. ]),
  267. "}",
  268. "return get(scope[key][version]);"
  269. ]
  270. )});`,
  271. `${cst} installedModules = {};`,
  272. `${cst} moduleToHandlerMapping = {`,
  273. Template.indent(
  274. Array.from(
  275. moduleIdToSourceMapping,
  276. ([key, source]) => `${JSON.stringify(key)}: ${source.source()}`
  277. ).join(",\n")
  278. ),
  279. "};",
  280. initialConsumes.length > 0
  281. ? Template.asString([
  282. `${cst} initialConsumes = ${JSON.stringify(initialConsumes)};`,
  283. `initialConsumes.forEach(${runtimeTemplate.basicFunction("id", [
  284. `${
  285. RuntimeGlobals.moduleFactories
  286. }[id] = ${runtimeTemplate.basicFunction("module", [
  287. "// Handle case when module is used sync",
  288. "installedModules[id] = 0;",
  289. `delete ${RuntimeGlobals.moduleCache}[id];`,
  290. `${cst} factory = moduleToHandlerMapping[id]();`,
  291. 'if(typeof factory !== "function") throw new Error("Shared module is not available for eager consumption: " + id);',
  292. "module.exports = factory();"
  293. ])}`
  294. ])});`
  295. ])
  296. : "// no consumes in initial chunks",
  297. this._runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers)
  298. ? Template.asString([
  299. `${cst} chunkMapping = ${JSON.stringify(
  300. chunkToModuleMapping,
  301. null,
  302. "\t"
  303. )};`,
  304. `${cst} startedInstallModules = {};`,
  305. `${
  306. RuntimeGlobals.ensureChunkHandlers
  307. }.consumes = ${runtimeTemplate.basicFunction("chunkId, promises", [
  308. `if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`,
  309. Template.indent([
  310. `chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction(
  311. "id",
  312. [
  313. `if(${RuntimeGlobals.hasOwnProperty}(installedModules, id)) return promises.push(installedModules[id]);`,
  314. "if(!startedInstallModules[id]) {",
  315. `${cst} onFactory = ${runtimeTemplate.basicFunction(
  316. "factory",
  317. [
  318. "installedModules[id] = 0;",
  319. `${
  320. RuntimeGlobals.moduleFactories
  321. }[id] = ${runtimeTemplate.basicFunction("module", [
  322. `delete ${RuntimeGlobals.moduleCache}[id];`,
  323. "module.exports = factory();"
  324. ])}`
  325. ]
  326. )};`,
  327. "startedInstallModules[id] = true;",
  328. `${cst} onError = ${runtimeTemplate.basicFunction("error", [
  329. "delete installedModules[id];",
  330. `${
  331. RuntimeGlobals.moduleFactories
  332. }[id] = ${runtimeTemplate.basicFunction("module", [
  333. `delete ${RuntimeGlobals.moduleCache}[id];`,
  334. "throw error;"
  335. ])}`
  336. ])};`,
  337. "try {",
  338. Template.indent([
  339. `${cst} promise = moduleToHandlerMapping[id]();`,
  340. "if(promise.then) {",
  341. Template.indent(
  342. "promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));"
  343. ),
  344. "} else onFactory(promise);"
  345. ]),
  346. "} catch(e) { onError(e); }",
  347. "}"
  348. ]
  349. )});`
  350. ]),
  351. "}"
  352. ])}`
  353. ])
  354. : "// no chunk loading of consumes"
  355. ]);
  356. }
  357. }
  358. module.exports = ConsumeSharedRuntimeModule;