a zip code crypto-currency system good for red ONLY

ModuleConcatenationPlugin.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
  7. const ModuleHotAcceptDependency = require("../dependencies/ModuleHotAcceptDependency");
  8. const ModuleHotDeclineDependency = require("../dependencies/ModuleHotDeclineDependency");
  9. const ConcatenatedModule = require("./ConcatenatedModule");
  10. const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
  11. const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
  12. function formatBailoutReason(msg) {
  13. return "ModuleConcatenation bailout: " + msg;
  14. }
  15. class ModuleConcatenationPlugin {
  16. constructor(options) {
  17. if(typeof options !== "object") options = {};
  18. this.options = options;
  19. }
  20. apply(compiler) {
  21. compiler.plugin("compilation", (compilation, params) => {
  22. params.normalModuleFactory.plugin("parser", (parser, parserOptions) => {
  23. parser.plugin("call eval", () => {
  24. parser.state.module.meta.hasEval = true;
  25. });
  26. });
  27. const bailoutReasonMap = new Map();
  28. function setBailoutReason(module, reason) {
  29. bailoutReasonMap.set(module, reason);
  30. module.optimizationBailout.push(typeof reason === "function" ? (rs) => formatBailoutReason(reason(rs)) : formatBailoutReason(reason));
  31. }
  32. function getBailoutReason(module, requestShortener) {
  33. const reason = bailoutReasonMap.get(module);
  34. if(typeof reason === "function") return reason(requestShortener);
  35. return reason;
  36. }
  37. compilation.plugin("optimize-chunk-modules", (chunks, modules) => {
  38. const relevantModules = [];
  39. const possibleInners = new Set();
  40. for(const module of modules) {
  41. // Only harmony modules are valid for optimization
  42. if(!module.meta || !module.meta.harmonyModule || !module.dependencies.some(d => d instanceof HarmonyCompatibilityDependency)) {
  43. setBailoutReason(module, "Module is not an ECMAScript module");
  44. continue;
  45. }
  46. // Because of variable renaming we can't use modules with eval
  47. if(module.meta && module.meta.hasEval) {
  48. setBailoutReason(module, "Module uses eval()");
  49. continue;
  50. }
  51. // Exports must be known (and not dynamic)
  52. if(!Array.isArray(module.providedExports)) {
  53. setBailoutReason(module, "Module exports are unknown");
  54. continue;
  55. }
  56. // Using dependency variables is not possible as this wraps the code in a function
  57. if(module.variables.length > 0) {
  58. setBailoutReason(module, `Module uses injected variables (${module.variables.map(v => v.name).join(", ")})`);
  59. continue;
  60. }
  61. // Hot Module Replacement need it's own module to work correctly
  62. if(module.dependencies.some(dep => dep instanceof ModuleHotAcceptDependency || dep instanceof ModuleHotDeclineDependency)) {
  63. setBailoutReason(module, "Module uses Hot Module Replacement");
  64. continue;
  65. }
  66. relevantModules.push(module);
  67. // Module must not be the entry points
  68. if(module.getChunks().some(chunk => chunk.entryModule === module)) {
  69. setBailoutReason(module, "Module is an entry point");
  70. continue;
  71. }
  72. // Module must only be used by Harmony Imports
  73. const nonHarmonyReasons = module.reasons.filter(reason => !(reason.dependency instanceof HarmonyImportDependency));
  74. if(nonHarmonyReasons.length > 0) {
  75. const importingModules = new Set(nonHarmonyReasons.map(r => r.module));
  76. const importingModuleTypes = new Map(Array.from(importingModules).map(m => [m, new Set(nonHarmonyReasons.filter(r => r.module === m).map(r => r.dependency.type).sort())]));
  77. setBailoutReason(module, (requestShortener) => {
  78. const names = Array.from(importingModules).map(m => `${m.readableIdentifier(requestShortener)} (referenced with ${Array.from(importingModuleTypes.get(m)).join(", ")})`).sort();
  79. return `Module is referenced from these modules with unsupported syntax: ${names.join(", ")}`;
  80. });
  81. continue;
  82. }
  83. possibleInners.add(module);
  84. }
  85. // sort by depth
  86. // modules with lower depth are more likely suited as roots
  87. // this improves performance, because modules already selected as inner are skipped
  88. relevantModules.sort((a, b) => {
  89. return a.depth - b.depth;
  90. });
  91. const concatConfigurations = [];
  92. const usedAsInner = new Set();
  93. for(const currentRoot of relevantModules) {
  94. // when used by another configuration as inner:
  95. // the other configuration is better and we can skip this one
  96. if(usedAsInner.has(currentRoot))
  97. continue;
  98. // create a configuration with the root
  99. const currentConfiguration = new ConcatConfiguration(currentRoot);
  100. // cache failures to add modules
  101. const failureCache = new Map();
  102. // try to add all imports
  103. for(const imp of this.getImports(currentRoot)) {
  104. const problem = this.tryToAdd(currentConfiguration, imp, possibleInners, failureCache);
  105. if(problem) {
  106. failureCache.set(imp, problem);
  107. currentConfiguration.addWarning(imp, problem);
  108. }
  109. }
  110. if(!currentConfiguration.isEmpty()) {
  111. concatConfigurations.push(currentConfiguration);
  112. for(const module of currentConfiguration.modules) {
  113. if(module !== currentConfiguration.rootModule)
  114. usedAsInner.add(module);
  115. }
  116. }
  117. }
  118. // HACK: Sort configurations by length and start with the longest one
  119. // to get the biggers groups possible. Used modules are marked with usedModules
  120. // TODO: Allow to reuse existing configuration while trying to add dependencies.
  121. // This would improve performance. O(n^2) -> O(n)
  122. concatConfigurations.sort((a, b) => {
  123. return b.modules.size - a.modules.size;
  124. });
  125. const usedModules = new Set();
  126. for(const concatConfiguration of concatConfigurations) {
  127. if(usedModules.has(concatConfiguration.rootModule))
  128. continue;
  129. const newModule = new ConcatenatedModule(concatConfiguration.rootModule, Array.from(concatConfiguration.modules));
  130. concatConfiguration.sortWarnings();
  131. for(const warning of concatConfiguration.warnings) {
  132. newModule.optimizationBailout.push((requestShortener) => {
  133. const reason = getBailoutReason(warning[0], requestShortener);
  134. const reasonWithPrefix = reason ? ` (<- ${reason})` : "";
  135. if(warning[0] === warning[1])
  136. return formatBailoutReason(`Cannot concat with ${warning[0].readableIdentifier(requestShortener)}${reasonWithPrefix}`);
  137. else
  138. return formatBailoutReason(`Cannot concat with ${warning[0].readableIdentifier(requestShortener)} because of ${warning[1].readableIdentifier(requestShortener)}${reasonWithPrefix}`);
  139. });
  140. }
  141. const chunks = concatConfiguration.rootModule.getChunks();
  142. for(const m of concatConfiguration.modules) {
  143. usedModules.add(m);
  144. chunks.forEach(chunk => chunk.removeModule(m));
  145. }
  146. chunks.forEach(chunk => {
  147. chunk.addModule(newModule);
  148. newModule.addChunk(chunk);
  149. if(chunk.entryModule === concatConfiguration.rootModule)
  150. chunk.entryModule = newModule;
  151. });
  152. compilation.modules.push(newModule);
  153. newModule.reasons.forEach(reason => reason.dependency.module = newModule);
  154. newModule.dependencies.forEach(dep => {
  155. if(dep.module) {
  156. dep.module.reasons.forEach(reason => {
  157. if(reason.dependency === dep)
  158. reason.module = newModule;
  159. });
  160. }
  161. });
  162. }
  163. compilation.modules = compilation.modules.filter(m => !usedModules.has(m));
  164. });
  165. });
  166. }
  167. getImports(module) {
  168. return Array.from(new Set(module.dependencies
  169. // Only harmony Dependencies
  170. .filter(dep => dep instanceof HarmonyImportDependency && dep.module)
  171. // Dependencies are simple enough to concat them
  172. .filter(dep => {
  173. return !module.dependencies.some(d =>
  174. d instanceof HarmonyExportImportedSpecifierDependency &&
  175. d.importDependency === dep &&
  176. !d.id &&
  177. !Array.isArray(dep.module.providedExports)
  178. );
  179. })
  180. // Take the imported module
  181. .map(dep => dep.module)
  182. ));
  183. }
  184. tryToAdd(config, module, possibleModules, failureCache) {
  185. const cacheEntry = failureCache.get(module);
  186. if(cacheEntry) {
  187. return cacheEntry;
  188. }
  189. // Already added?
  190. if(config.has(module)) {
  191. return null;
  192. }
  193. // Not possible to add?
  194. if(!possibleModules.has(module)) {
  195. failureCache.set(module, module); // cache failures for performance
  196. return module;
  197. }
  198. // module must be in the same chunks
  199. if(!config.rootModule.hasEqualsChunks(module)) {
  200. failureCache.set(module, module); // cache failures for performance
  201. return module;
  202. }
  203. // Clone config to make experimental changes
  204. const testConfig = config.clone();
  205. // Add the module
  206. testConfig.add(module);
  207. // Every module which depends on the added module must be in the configuration too.
  208. for(const reason of module.reasons) {
  209. const problem = this.tryToAdd(testConfig, reason.module, possibleModules, failureCache);
  210. if(problem) {
  211. failureCache.set(module, problem); // cache failures for performance
  212. return problem;
  213. }
  214. }
  215. // Eagerly try to add imports too if possible
  216. for(const imp of this.getImports(module)) {
  217. const problem = this.tryToAdd(testConfig, imp, possibleModules, failureCache);
  218. if(problem) {
  219. config.addWarning(module, problem);
  220. }
  221. }
  222. // Commit experimental changes
  223. config.set(testConfig);
  224. return null;
  225. }
  226. }
  227. class ConcatConfiguration {
  228. constructor(rootModule) {
  229. this.rootModule = rootModule;
  230. this.modules = new Set([rootModule]);
  231. this.warnings = new Map();
  232. }
  233. add(module) {
  234. this.modules.add(module);
  235. }
  236. has(module) {
  237. return this.modules.has(module);
  238. }
  239. isEmpty() {
  240. return this.modules.size === 1;
  241. }
  242. addWarning(module, problem) {
  243. this.warnings.set(module, problem);
  244. }
  245. sortWarnings() {
  246. this.warnings = new Map(Array.from(this.warnings).sort((a, b) => {
  247. const ai = a[0].identifier();
  248. const bi = b[0].identifier();
  249. if(ai < bi) return -1;
  250. if(ai > bi) return 1;
  251. return 0;
  252. }));
  253. }
  254. clone() {
  255. const clone = new ConcatConfiguration(this.rootModule);
  256. for(const module of this.modules)
  257. clone.add(module);
  258. for(const pair of this.warnings)
  259. clone.addWarning(pair[0], pair[1]);
  260. return clone;
  261. }
  262. set(config) {
  263. this.rootModule = config.rootModule;
  264. this.modules = new Set(config.modules);
  265. this.warnings = new Map(config.warnings);
  266. }
  267. }
  268. module.exports = ModuleConcatenationPlugin;