a zip code crypto-currency system good for red ONLY

CommonsChunkPlugin.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. let nextIdent = 0;
  7. class CommonsChunkPlugin {
  8. constructor(options) {
  9. if(arguments.length > 1) {
  10. throw new Error(`Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
  11. object *or* the name of the chunk.
  12. Example: if your old code looked like this:
  13. new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js')
  14. You would change it to:
  15. new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' })
  16. The available options are:
  17. name: string
  18. names: string[]
  19. filename: string
  20. minChunks: number
  21. chunks: string[]
  22. children: boolean
  23. async: boolean
  24. minSize: number`);
  25. }
  26. const normalizedOptions = this.normalizeOptions(options);
  27. this.chunkNames = normalizedOptions.chunkNames;
  28. this.filenameTemplate = normalizedOptions.filenameTemplate;
  29. this.minChunks = normalizedOptions.minChunks;
  30. this.selectedChunks = normalizedOptions.selectedChunks;
  31. this.children = normalizedOptions.children;
  32. this.deepChildren = normalizedOptions.deepChildren;
  33. this.async = normalizedOptions.async;
  34. this.minSize = normalizedOptions.minSize;
  35. this.ident = __filename + (nextIdent++);
  36. }
  37. normalizeOptions(options) {
  38. if(Array.isArray(options)) {
  39. return {
  40. chunkNames: options,
  41. };
  42. }
  43. if(typeof options === "string") {
  44. return {
  45. chunkNames: [options],
  46. };
  47. }
  48. // options.children and options.chunk may not be used together
  49. if(options.children && options.chunks) {
  50. throw new Error("You can't and it does not make any sense to use \"children\" and \"chunk\" options together.");
  51. }
  52. /**
  53. * options.async and options.filename are also not possible together
  54. * as filename specifies how the chunk is called but "async" implies
  55. * that webpack will take care of loading this file.
  56. */
  57. if(options.async && options.filename) {
  58. throw new Error(`You can not specify a filename if you use the "async" option.
  59. You can however specify the name of the async chunk by passing the desired string as the "async" option.`);
  60. }
  61. /**
  62. * Make sure this is either an array or undefined.
  63. * "name" can be a string and
  64. * "names" a string or an array
  65. */
  66. const chunkNames = options.name || options.names ? [].concat(options.name || options.names) : undefined;
  67. return {
  68. chunkNames: chunkNames,
  69. filenameTemplate: options.filename,
  70. minChunks: options.minChunks,
  71. selectedChunks: options.chunks,
  72. children: options.children,
  73. deepChildren: options.deepChildren,
  74. async: options.async,
  75. minSize: options.minSize
  76. };
  77. }
  78. apply(compiler) {
  79. compiler.plugin("this-compilation", (compilation) => {
  80. compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], (chunks) => {
  81. // only optimize once
  82. if(compilation[this.ident]) return;
  83. compilation[this.ident] = true;
  84. /**
  85. * Creates a list of "common"" chunks based on the options.
  86. * The list is made up of preexisting or newly created chunks.
  87. * - If chunk has the name as specified in the chunkNames it is put in the list
  88. * - If no chunk with the name as given in chunkNames exists a new chunk is created and added to the list
  89. *
  90. * These chunks are the "targets" for extracted modules.
  91. */
  92. const targetChunks = this.getTargetChunks(chunks, compilation, this.chunkNames, this.children, this.async);
  93. // iterate over all our new chunks
  94. targetChunks.forEach((targetChunk, idx) => {
  95. /**
  96. * These chunks are subject to get "common" modules extracted and moved to the common chunk
  97. */
  98. const affectedChunks = this.getAffectedChunks(compilation, chunks, targetChunk, targetChunks, idx, this.selectedChunks, this.async, this.children, this.deepChildren);
  99. // bail if no chunk is affected
  100. if(!affectedChunks) {
  101. return;
  102. }
  103. // If we are async create an async chunk now
  104. // override the "commonChunk" with the newly created async one and use it as commonChunk from now on
  105. let asyncChunk;
  106. if(this.async) {
  107. // If async chunk is one of the affected chunks, just use it
  108. asyncChunk = affectedChunks.filter(c => c.name === this.async)[0];
  109. // Elsewise create a new one
  110. if(!asyncChunk) {
  111. asyncChunk = this.createAsyncChunk(
  112. compilation,
  113. targetChunks.length <= 1 || typeof this.async !== "string" ? this.async :
  114. targetChunk.name ? `${this.async}-${targetChunk.name}` :
  115. true,
  116. targetChunk
  117. );
  118. }
  119. targetChunk = asyncChunk;
  120. }
  121. /**
  122. * Check which modules are "common" and could be extracted to a "common" chunk
  123. */
  124. const extractableModules = this.getExtractableModules(this.minChunks, affectedChunks, targetChunk);
  125. // If the minSize option is set check if the size extracted from the chunk is reached
  126. // else bail out here.
  127. // As all modules/commons are interlinked with each other, common modules would be extracted
  128. // if we reach this mark at a later common chunk. (quirky I guess).
  129. if(this.minSize) {
  130. const modulesSize = this.calculateModulesSize(extractableModules);
  131. // if too small, bail
  132. if(modulesSize < this.minSize)
  133. return;
  134. }
  135. // Remove modules that are moved to commons chunk from their original chunks
  136. // return all chunks that are affected by having modules removed - we need them later (apparently)
  137. const chunksWithExtractedModules = this.extractModulesAndReturnAffectedChunks(extractableModules, affectedChunks);
  138. // connect all extracted modules with the common chunk
  139. this.addExtractedModulesToTargetChunk(targetChunk, extractableModules);
  140. // set filenameTemplate for chunk
  141. if(this.filenameTemplate)
  142. targetChunk.filenameTemplate = this.filenameTemplate;
  143. // if we are async connect the blocks of the "reallyUsedChunk" - the ones that had modules removed -
  144. // with the commonChunk and get the origins for the asyncChunk (remember "asyncChunk === commonChunk" at this moment).
  145. // bail out
  146. if(this.async) {
  147. this.moveExtractedChunkBlocksToTargetChunk(chunksWithExtractedModules, targetChunk);
  148. asyncChunk.origins = this.extractOriginsOfChunksWithExtractedModules(chunksWithExtractedModules);
  149. return;
  150. }
  151. // we are not in "async" mode
  152. // connect used chunks with commonChunk - shouldnt this be reallyUsedChunks here?
  153. this.makeTargetChunkParentOfAffectedChunks(affectedChunks, targetChunk);
  154. });
  155. return true;
  156. });
  157. });
  158. }
  159. getTargetChunks(allChunks, compilation, chunkNames, children, asyncOption) {
  160. const asyncOrNoSelectedChunk = children || asyncOption;
  161. // we have specified chunk names
  162. if(chunkNames) {
  163. // map chunks by chunkName for quick access
  164. const allChunksNameMap = allChunks.reduce((map, chunk) => {
  165. if(chunk.name) {
  166. map.set(chunk.name, chunk);
  167. }
  168. return map;
  169. }, new Map());
  170. // Ensure we have a chunk per specified chunk name.
  171. // Reuse existing chunks if possible
  172. return chunkNames.map(chunkName => {
  173. if(allChunksNameMap.has(chunkName)) {
  174. return allChunksNameMap.get(chunkName);
  175. }
  176. // add the filtered chunks to the compilation
  177. return compilation.addChunk(chunkName);
  178. });
  179. }
  180. // we dont have named chunks specified, so we just take all of them
  181. if(asyncOrNoSelectedChunk) {
  182. return allChunks;
  183. }
  184. /**
  185. * No chunk name(s) was specified nor is this an async/children commons chunk
  186. */
  187. throw new Error(`You did not specify any valid target chunk settings.
  188. Take a look at the "name"/"names" or async/children option.`);
  189. }
  190. getAffectedUnnamedChunks(affectedChunks, targetChunk, rootChunk, asyncOption, deepChildrenOption) {
  191. let chunks = targetChunk.chunks;
  192. chunks && chunks.forEach((chunk) => {
  193. if(chunk.isInitial()) {
  194. return;
  195. }
  196. // If all the parents of a chunk are either
  197. // a) the target chunk we started with
  198. // b) themselves affected chunks
  199. // we can assume that this chunk is an affected chunk too, as there is no way a chunk that
  200. // isn't only depending on the target chunk is a parent of the chunk tested
  201. if(asyncOption || chunk.parents.every((parentChunk) => parentChunk === rootChunk || affectedChunks.has(parentChunk))) {
  202. // This check not only dedupes the affectedChunks but also guarantees we avoid endless loops
  203. if(!affectedChunks.has(chunk)) {
  204. // We mutate the affected chunks before going deeper, so the deeper levels and other branches
  205. // have the information of this chunk being affected for their assertion if a chunk should
  206. // not be affected
  207. affectedChunks.add(chunk);
  208. // We recurse down to all the children of the chunk, applying the same assumption.
  209. // This guarantees that if a chunk should be an affected chunk,
  210. // at the latest the last connection to the same chunk meets the
  211. // condition to add it to the affected chunks.
  212. if(deepChildrenOption === true) {
  213. this.getAffectedUnnamedChunks(affectedChunks, chunk, rootChunk, asyncOption, deepChildrenOption);
  214. }
  215. }
  216. }
  217. });
  218. }
  219. getAffectedChunks(compilation, allChunks, targetChunk, targetChunks, currentIndex, selectedChunks, asyncOption, childrenOption, deepChildrenOption) {
  220. const asyncOrNoSelectedChunk = childrenOption || asyncOption;
  221. if(Array.isArray(selectedChunks)) {
  222. return allChunks.filter(chunk => {
  223. const notCommmonChunk = chunk !== targetChunk;
  224. const isSelectedChunk = selectedChunks.indexOf(chunk.name) > -1;
  225. return notCommmonChunk && isSelectedChunk;
  226. });
  227. }
  228. if(asyncOrNoSelectedChunk) {
  229. let affectedChunks = new Set();
  230. this.getAffectedUnnamedChunks(affectedChunks, targetChunk, targetChunk, asyncOption, deepChildrenOption);
  231. return Array.from(affectedChunks);
  232. }
  233. /**
  234. * past this point only entry chunks are allowed to become commonChunks
  235. */
  236. if(targetChunk.parents.length > 0) {
  237. compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + targetChunk.name + ")"));
  238. return;
  239. }
  240. /**
  241. * If we find a "targetchunk" that is also a normal chunk (meaning it is probably specified as an entry)
  242. * and the current target chunk comes after that and the found chunk has a runtime*
  243. * make that chunk be an 'affected' chunk of the current target chunk.
  244. *
  245. * To understand what that means take a look at the "examples/chunkhash", this basically will
  246. * result in the runtime to be extracted to the current target chunk.
  247. *
  248. * *runtime: the "runtime" is the "webpack"-block you may have seen in the bundles that resolves modules etc.
  249. */
  250. return allChunks.filter((chunk) => {
  251. const found = targetChunks.indexOf(chunk);
  252. if(found >= currentIndex) return false;
  253. return chunk.hasRuntime();
  254. });
  255. }
  256. createAsyncChunk(compilation, asyncOption, targetChunk) {
  257. const asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
  258. asyncChunk.chunkReason = "async commons chunk";
  259. asyncChunk.extraAsync = true;
  260. asyncChunk.addParent(targetChunk);
  261. targetChunk.addChunk(asyncChunk);
  262. return asyncChunk;
  263. }
  264. // If minChunks is a function use that
  265. // otherwhise check if a module is used at least minChunks or 2 or usedChunks.length time
  266. getModuleFilter(minChunks, targetChunk, usedChunksLength) {
  267. if(typeof minChunks === "function") {
  268. return minChunks;
  269. }
  270. const minCount = (minChunks || Math.max(2, usedChunksLength));
  271. const isUsedAtLeastMinTimes = (module, count) => count >= minCount;
  272. return isUsedAtLeastMinTimes;
  273. }
  274. getExtractableModules(minChunks, usedChunks, targetChunk) {
  275. if(minChunks === Infinity) {
  276. return [];
  277. }
  278. // count how many chunks contain a module
  279. const commonModulesToCountMap = usedChunks.reduce((map, chunk) => {
  280. for(const module of chunk.modulesIterable) {
  281. const count = map.has(module) ? map.get(module) : 0;
  282. map.set(module, count + 1);
  283. }
  284. return map;
  285. }, new Map());
  286. // filter by minChunks
  287. const moduleFilterCount = this.getModuleFilter(minChunks, targetChunk, usedChunks.length);
  288. // filter by condition
  289. const moduleFilterCondition = (module, chunk) => {
  290. if(!module.chunkCondition) {
  291. return true;
  292. }
  293. return module.chunkCondition(chunk);
  294. };
  295. return Array.from(commonModulesToCountMap).filter(entry => {
  296. const module = entry[0];
  297. const count = entry[1];
  298. // if the module passes both filters, keep it.
  299. return moduleFilterCount(module, count) && moduleFilterCondition(module, targetChunk);
  300. }).map(entry => entry[0]);
  301. }
  302. calculateModulesSize(modules) {
  303. return modules.reduce((totalSize, module) => totalSize + module.size(), 0);
  304. }
  305. extractModulesAndReturnAffectedChunks(reallyUsedModules, usedChunks) {
  306. return reallyUsedModules.reduce((affectedChunksSet, module) => {
  307. for(const chunk of usedChunks) {
  308. // removeChunk returns true if the chunk was contained and succesfully removed
  309. // false if the module did not have a connection to the chunk in question
  310. if(module.removeChunk(chunk)) {
  311. affectedChunksSet.add(chunk);
  312. }
  313. }
  314. return affectedChunksSet;
  315. }, new Set());
  316. }
  317. addExtractedModulesToTargetChunk(chunk, modules) {
  318. for(const module of modules) {
  319. chunk.addModule(module);
  320. module.addChunk(chunk);
  321. }
  322. }
  323. makeTargetChunkParentOfAffectedChunks(usedChunks, commonChunk) {
  324. for(const chunk of usedChunks) {
  325. // set commonChunk as new sole parent
  326. chunk.parents = [commonChunk];
  327. // add chunk to commonChunk
  328. commonChunk.addChunk(chunk);
  329. for(const entrypoint of chunk.entrypoints) {
  330. entrypoint.insertChunk(commonChunk, chunk);
  331. }
  332. }
  333. }
  334. moveExtractedChunkBlocksToTargetChunk(chunks, targetChunk) {
  335. for(const chunk of chunks) {
  336. if(chunk === targetChunk) continue;
  337. for(const block of chunk.blocks) {
  338. if(block.chunks.indexOf(targetChunk) === -1) {
  339. block.chunks.unshift(targetChunk);
  340. }
  341. targetChunk.addBlock(block);
  342. }
  343. }
  344. }
  345. extractOriginsOfChunksWithExtractedModules(chunks) {
  346. const origins = [];
  347. for(const chunk of chunks) {
  348. for(const origin of chunk.origins) {
  349. const newOrigin = Object.create(origin);
  350. newOrigin.reasons = (origin.reasons || []).concat("async commons");
  351. origins.push(newOrigin);
  352. }
  353. }
  354. return origins;
  355. }
  356. }
  357. module.exports = CommonsChunkPlugin;