ConsumeSharedRuntimeModule.js 13 KB

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