ShareRuntimeModule.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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. compareModulesByIdentifier,
  11. compareStrings
  12. } = require("../util/comparators");
  13. /** @typedef {import("../Chunk")} Chunk */
  14. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  15. /** @typedef {import("../Compilation")} Compilation */
  16. class ShareRuntimeModule extends RuntimeModule {
  17. constructor() {
  18. super("sharing");
  19. }
  20. /**
  21. * @returns {string | null} runtime code
  22. */
  23. generate() {
  24. const compilation = /** @type {Compilation} */ (this.compilation);
  25. const {
  26. runtimeTemplate,
  27. codeGenerationResults,
  28. outputOptions: { uniqueName, ignoreBrowserWarnings }
  29. } = compilation;
  30. const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
  31. /** @type {Map<string, Map<number, Set<string>>>} */
  32. const initCodePerScope = new Map();
  33. for (const chunk of /** @type {Chunk} */ (
  34. this.chunk
  35. ).getAllReferencedChunks()) {
  36. const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
  37. chunk,
  38. "share-init",
  39. compareModulesByIdentifier
  40. );
  41. if (!modules) continue;
  42. for (const m of modules) {
  43. const data = codeGenerationResults.getData(
  44. m,
  45. chunk.runtime,
  46. "share-init"
  47. );
  48. if (!data) continue;
  49. for (const item of data) {
  50. const { shareScope, initStage, init } = item;
  51. let stages = initCodePerScope.get(shareScope);
  52. if (stages === undefined) {
  53. initCodePerScope.set(shareScope, (stages = new Map()));
  54. }
  55. let list = stages.get(initStage || 0);
  56. if (list === undefined) {
  57. stages.set(initStage || 0, (list = new Set()));
  58. }
  59. list.add(init);
  60. }
  61. }
  62. }
  63. return Template.asString([
  64. `${RuntimeGlobals.shareScopeMap} = {};`,
  65. "var initPromises = {};",
  66. "var initTokens = {};",
  67. `${RuntimeGlobals.initializeSharing} = ${runtimeTemplate.basicFunction(
  68. "name, initScope",
  69. [
  70. "if(!initScope) initScope = [];",
  71. "// handling circular init calls",
  72. "var initToken = initTokens[name];",
  73. "if(!initToken) initToken = initTokens[name] = {};",
  74. "if(initScope.indexOf(initToken) >= 0) return;",
  75. "initScope.push(initToken);",
  76. "// only runs once",
  77. "if(initPromises[name]) return initPromises[name];",
  78. "// creates a new share scope if needed",
  79. `if(!${RuntimeGlobals.hasOwnProperty}(${RuntimeGlobals.shareScopeMap}, name)) ${RuntimeGlobals.shareScopeMap}[name] = {};`,
  80. "// runs all init snippets from all modules reachable",
  81. `var scope = ${RuntimeGlobals.shareScopeMap}[name];`,
  82. `var warn = ${
  83. ignoreBrowserWarnings
  84. ? runtimeTemplate.basicFunction("", "")
  85. : runtimeTemplate.basicFunction("msg", [
  86. 'if (typeof console !== "undefined" && console.warn) console.warn(msg);'
  87. ])
  88. };`,
  89. `var uniqueName = ${JSON.stringify(uniqueName || undefined)};`,
  90. `var register = ${runtimeTemplate.basicFunction(
  91. "name, version, factory, eager",
  92. [
  93. "var versions = scope[name] = scope[name] || {};",
  94. "var activeVersion = versions[version];",
  95. "if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };"
  96. ]
  97. )};`,
  98. `var initExternal = ${runtimeTemplate.basicFunction("id", [
  99. `var handleError = ${runtimeTemplate.expressionFunction(
  100. 'warn("Initialization of sharing external failed: " + err)',
  101. "err"
  102. )};`,
  103. "try {",
  104. Template.indent([
  105. `var module = ${RuntimeGlobals.require}(id);`,
  106. "if(!module) return;",
  107. `var initFn = ${runtimeTemplate.returningFunction(
  108. `module && module.init && module.init(${RuntimeGlobals.shareScopeMap}[name], initScope)`,
  109. "module"
  110. )}`,
  111. "if(module.then) return promises.push(module.then(initFn, handleError));",
  112. "var initResult = initFn(module);",
  113. "if(initResult && initResult.then) return promises.push(initResult['catch'](handleError));"
  114. ]),
  115. "} catch(err) { handleError(err); }"
  116. ])}`,
  117. "var promises = [];",
  118. "switch(name) {",
  119. ...Array.from(initCodePerScope)
  120. .sort(([a], [b]) => compareStrings(a, b))
  121. .map(([name, stages]) =>
  122. Template.indent([
  123. `case ${JSON.stringify(name)}: {`,
  124. Template.indent(
  125. Array.from(stages)
  126. .sort(([a], [b]) => a - b)
  127. .map(([, initCode]) =>
  128. Template.asString(Array.from(initCode))
  129. )
  130. ),
  131. "}",
  132. "break;"
  133. ])
  134. ),
  135. "}",
  136. "if(!promises.length) return initPromises[name] = 1;",
  137. `return initPromises[name] = Promise.all(promises).then(${runtimeTemplate.returningFunction(
  138. "initPromises[name] = 1"
  139. )});`
  140. ]
  141. )};`
  142. ]);
  143. }
  144. }
  145. module.exports = ShareRuntimeModule;