ConsumeSharedRuntimeModule.js 13 KB

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