ConsumeSharedRuntimeModule.js 13 KB

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