SplitChunksPlugin.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const crypto = require("crypto");
  7. const SortableSet = require("../util/SortableSet");
  8. const GraphHelpers = require("../GraphHelpers");
  9. const { isSubset } = require("../util/SetHelpers");
  10. const deterministicGrouping = require("../util/deterministicGrouping");
  11. const contextify = require("../util/identifier").contextify;
  12. /** @typedef {import("../Compiler")} Compiler */
  13. /** @typedef {import("../Chunk")} Chunk */
  14. /** @typedef {import("../Module")} Module */
  15. /** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */
  16. /** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
  17. const deterministicGroupingForModules = /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (deterministicGrouping);
  18. const hashFilename = name => {
  19. return crypto
  20. .createHash("md4")
  21. .update(name)
  22. .digest("hex")
  23. .slice(0, 8);
  24. };
  25. const sortByIdentifier = (a, b) => {
  26. if (a.identifier() > b.identifier()) return 1;
  27. if (a.identifier() < b.identifier()) return -1;
  28. return 0;
  29. };
  30. const getRequests = chunk => {
  31. let requests = 0;
  32. for (const chunkGroup of chunk.groupsIterable) {
  33. requests = Math.max(requests, chunkGroup.chunks.length);
  34. }
  35. return requests;
  36. };
  37. const getModulesSize = modules => {
  38. let sum = 0;
  39. for (const m of modules) {
  40. sum += m.size();
  41. }
  42. return sum;
  43. };
  44. /**
  45. * @template T
  46. * @param {Set<T>} a set
  47. * @param {Set<T>} b other set
  48. * @returns {boolean} true if at least one item of a is in b
  49. */
  50. const isOverlap = (a, b) => {
  51. for (const item of a) {
  52. if (b.has(item)) return true;
  53. }
  54. return false;
  55. };
  56. const compareEntries = (a, b) => {
  57. // 1. by priority
  58. const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
  59. if (diffPriority) return diffPriority;
  60. // 2. by number of chunks
  61. const diffCount = a.chunks.size - b.chunks.size;
  62. if (diffCount) return diffCount;
  63. // 3. by size reduction
  64. const aSizeReduce = a.size * (a.chunks.size - 1);
  65. const bSizeReduce = b.size * (b.chunks.size - 1);
  66. const diffSizeReduce = aSizeReduce - bSizeReduce;
  67. if (diffSizeReduce) return diffSizeReduce;
  68. // 4. by number of modules (to be able to compare by identifier)
  69. const modulesA = a.modules;
  70. const modulesB = b.modules;
  71. const diff = modulesA.size - modulesB.size;
  72. if (diff) return diff;
  73. // 5. by module identifiers
  74. modulesA.sort();
  75. modulesB.sort();
  76. const aI = modulesA[Symbol.iterator]();
  77. const bI = modulesB[Symbol.iterator]();
  78. // eslint-disable-next-line no-constant-condition
  79. while (true) {
  80. const aItem = aI.next();
  81. const bItem = bI.next();
  82. if (aItem.done) return 0;
  83. const aModuleIdentifier = aItem.value.identifier();
  84. const bModuleIdentifier = bItem.value.identifier();
  85. if (aModuleIdentifier > bModuleIdentifier) return -1;
  86. if (aModuleIdentifier < bModuleIdentifier) return 1;
  87. }
  88. };
  89. const INITIAL_CHUNK_FILTER = chunk => chunk.canBeInitial();
  90. const ASYNC_CHUNK_FILTER = chunk => !chunk.canBeInitial();
  91. const ALL_CHUNK_FILTER = chunk => true;
  92. module.exports = class SplitChunksPlugin {
  93. constructor(options) {
  94. this.options = SplitChunksPlugin.normalizeOptions(options);
  95. }
  96. static normalizeOptions(options = {}) {
  97. return {
  98. chunksFilter: SplitChunksPlugin.normalizeChunksFilter(
  99. options.chunks || "all"
  100. ),
  101. minSize: options.minSize || 0,
  102. maxSize: options.maxSize || 0,
  103. minChunks: options.minChunks || 1,
  104. maxAsyncRequests: options.maxAsyncRequests || 1,
  105. maxInitialRequests: options.maxInitialRequests || 1,
  106. hidePathInfo: options.hidePathInfo || false,
  107. filename: options.filename || undefined,
  108. getCacheGroups: SplitChunksPlugin.normalizeCacheGroups({
  109. cacheGroups: options.cacheGroups,
  110. name: options.name,
  111. automaticNameDelimiter: options.automaticNameDelimiter
  112. }),
  113. automaticNameDelimiter: options.automaticNameDelimiter,
  114. fallbackCacheGroup: SplitChunksPlugin.normalizeFallbackCacheGroup(
  115. options.fallbackCacheGroup || {},
  116. options
  117. )
  118. };
  119. }
  120. static normalizeName({ name, automaticNameDelimiter, automaticNamePrefix }) {
  121. if (name === true) {
  122. /** @type {WeakMap<Chunk[], Record<string, string>>} */
  123. const cache = new WeakMap();
  124. const fn = (module, chunks, cacheGroup) => {
  125. let cacheEntry = cache.get(chunks);
  126. if (cacheEntry === undefined) {
  127. cacheEntry = {};
  128. cache.set(chunks, cacheEntry);
  129. } else if (cacheGroup in cacheEntry) {
  130. return cacheEntry[cacheGroup];
  131. }
  132. const names = chunks.map(c => c.name);
  133. if (!names.every(Boolean)) {
  134. cacheEntry[cacheGroup] = undefined;
  135. return;
  136. }
  137. names.sort();
  138. const prefix =
  139. typeof automaticNamePrefix === "string"
  140. ? automaticNamePrefix
  141. : cacheGroup;
  142. const namePrefix = prefix ? prefix + automaticNameDelimiter : "";
  143. let name = namePrefix + names.join(automaticNameDelimiter);
  144. // Filenames and paths can't be too long otherwise an
  145. // ENAMETOOLONG error is raised. If the generated name if too
  146. // long, it is truncated and a hash is appended. The limit has
  147. // been set to 100 to prevent `[name].[chunkhash].[ext]` from
  148. // generating a 256+ character string.
  149. if (name.length > 100) {
  150. name =
  151. name.slice(0, 100) + automaticNameDelimiter + hashFilename(name);
  152. }
  153. cacheEntry[cacheGroup] = name;
  154. return name;
  155. };
  156. return fn;
  157. }
  158. if (typeof name === "string") {
  159. const fn = () => {
  160. return name;
  161. };
  162. return fn;
  163. }
  164. if (typeof name === "function") return name;
  165. }
  166. static normalizeChunksFilter(chunks) {
  167. if (chunks === "initial") {
  168. return INITIAL_CHUNK_FILTER;
  169. }
  170. if (chunks === "async") {
  171. return ASYNC_CHUNK_FILTER;
  172. }
  173. if (chunks === "all") {
  174. return ALL_CHUNK_FILTER;
  175. }
  176. if (typeof chunks === "function") return chunks;
  177. }
  178. static normalizeFallbackCacheGroup(
  179. {
  180. minSize = undefined,
  181. maxSize = undefined,
  182. automaticNameDelimiter = undefined
  183. },
  184. {
  185. minSize: defaultMinSize = undefined,
  186. maxSize: defaultMaxSize = undefined,
  187. automaticNameDelimiter: defaultAutomaticNameDelimiter = undefined
  188. }
  189. ) {
  190. return {
  191. minSize: typeof minSize === "number" ? minSize : defaultMinSize || 0,
  192. maxSize: typeof maxSize === "number" ? maxSize : defaultMaxSize || 0,
  193. automaticNameDelimiter:
  194. automaticNameDelimiter || defaultAutomaticNameDelimiter || "~"
  195. };
  196. }
  197. static normalizeCacheGroups({ cacheGroups, name, automaticNameDelimiter }) {
  198. if (typeof cacheGroups === "function") {
  199. // TODO webpack 5 remove this
  200. if (cacheGroups.length !== 1) {
  201. return module => cacheGroups(module, module.getChunks());
  202. }
  203. return cacheGroups;
  204. }
  205. if (cacheGroups && typeof cacheGroups === "object") {
  206. const fn = module => {
  207. let results;
  208. for (const key of Object.keys(cacheGroups)) {
  209. let option = cacheGroups[key];
  210. if (option === false) continue;
  211. if (option instanceof RegExp || typeof option === "string") {
  212. option = {
  213. test: option
  214. };
  215. }
  216. if (typeof option === "function") {
  217. let result = option(module);
  218. if (result) {
  219. if (results === undefined) results = [];
  220. for (const r of Array.isArray(result) ? result : [result]) {
  221. const result = Object.assign({ key }, r);
  222. if (result.name) result.getName = () => result.name;
  223. if (result.chunks) {
  224. result.chunksFilter = SplitChunksPlugin.normalizeChunksFilter(
  225. result.chunks
  226. );
  227. }
  228. results.push(result);
  229. }
  230. }
  231. } else if (SplitChunksPlugin.checkTest(option.test, module)) {
  232. if (results === undefined) results = [];
  233. results.push({
  234. key: key,
  235. priority: option.priority,
  236. getName:
  237. SplitChunksPlugin.normalizeName({
  238. name: option.name || name,
  239. automaticNameDelimiter:
  240. typeof option.automaticNameDelimiter === "string"
  241. ? option.automaticNameDelimiter
  242. : automaticNameDelimiter,
  243. automaticNamePrefix: option.automaticNamePrefix
  244. }) || (() => {}),
  245. chunksFilter: SplitChunksPlugin.normalizeChunksFilter(
  246. option.chunks
  247. ),
  248. enforce: option.enforce,
  249. minSize: option.minSize,
  250. maxSize: option.maxSize,
  251. minChunks: option.minChunks,
  252. maxAsyncRequests: option.maxAsyncRequests,
  253. maxInitialRequests: option.maxInitialRequests,
  254. filename: option.filename,
  255. reuseExistingChunk: option.reuseExistingChunk
  256. });
  257. }
  258. }
  259. return results;
  260. };
  261. return fn;
  262. }
  263. const fn = () => {};
  264. return fn;
  265. }
  266. static checkTest(test, module) {
  267. if (test === undefined) return true;
  268. if (typeof test === "function") {
  269. if (test.length !== 1) {
  270. return test(module, module.getChunks());
  271. }
  272. return test(module);
  273. }
  274. if (typeof test === "boolean") return test;
  275. if (typeof test === "string") {
  276. if (
  277. module.nameForCondition &&
  278. module.nameForCondition().startsWith(test)
  279. ) {
  280. return true;
  281. }
  282. for (const chunk of module.chunksIterable) {
  283. if (chunk.name && chunk.name.startsWith(test)) {
  284. return true;
  285. }
  286. }
  287. return false;
  288. }
  289. if (test instanceof RegExp) {
  290. if (module.nameForCondition && test.test(module.nameForCondition())) {
  291. return true;
  292. }
  293. for (const chunk of module.chunksIterable) {
  294. if (chunk.name && test.test(chunk.name)) {
  295. return true;
  296. }
  297. }
  298. return false;
  299. }
  300. return false;
  301. }
  302. /**
  303. * @param {Compiler} compiler webpack compiler
  304. * @returns {void}
  305. */
  306. apply(compiler) {
  307. compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
  308. let alreadyOptimized = false;
  309. compilation.hooks.unseal.tap("SplitChunksPlugin", () => {
  310. alreadyOptimized = false;
  311. });
  312. compilation.hooks.optimizeChunksAdvanced.tap(
  313. "SplitChunksPlugin",
  314. chunks => {
  315. if (alreadyOptimized) return;
  316. alreadyOptimized = true;
  317. // Give each selected chunk an index (to create strings from chunks)
  318. const indexMap = new Map();
  319. let index = 1;
  320. for (const chunk of chunks) {
  321. indexMap.set(chunk, index++);
  322. }
  323. const getKey = chunks => {
  324. return Array.from(chunks, c => indexMap.get(c))
  325. .sort()
  326. .join();
  327. };
  328. /** @type {Map<string, Set<Chunk>>} */
  329. const chunkSetsInGraph = new Map();
  330. for (const module of compilation.modules) {
  331. const chunksKey = getKey(module.chunksIterable);
  332. if (!chunkSetsInGraph.has(chunksKey)) {
  333. chunkSetsInGraph.set(chunksKey, new Set(module.chunksIterable));
  334. }
  335. }
  336. // group these set of chunks by count
  337. // to allow to check less sets via isSubset
  338. // (only smaller sets can be subset)
  339. /** @type {Map<number, Array<Set<Chunk>>>} */
  340. const chunkSetsByCount = new Map();
  341. for (const chunksSet of chunkSetsInGraph.values()) {
  342. const count = chunksSet.size;
  343. let array = chunkSetsByCount.get(count);
  344. if (array === undefined) {
  345. array = [];
  346. chunkSetsByCount.set(count, array);
  347. }
  348. array.push(chunksSet);
  349. }
  350. // Create a list of possible combinations
  351. const combinationsCache = new Map(); // Map<string, Set<Chunk>[]>
  352. const getCombinations = key => {
  353. const chunksSet = chunkSetsInGraph.get(key);
  354. var array = [chunksSet];
  355. if (chunksSet.size > 1) {
  356. for (const [count, setArray] of chunkSetsByCount) {
  357. // "equal" is not needed because they would have been merge in the first step
  358. if (count < chunksSet.size) {
  359. for (const set of setArray) {
  360. if (isSubset(chunksSet, set)) {
  361. array.push(set);
  362. }
  363. }
  364. }
  365. }
  366. }
  367. return array;
  368. };
  369. /**
  370. * @typedef {Object} SelectedChunksResult
  371. * @property {Chunk[]} chunks the list of chunks
  372. * @property {string} key a key of the list
  373. */
  374. /**
  375. * @typedef {function(Chunk): boolean} ChunkFilterFunction
  376. */
  377. /** @type {WeakMap<Set<Chunk>, WeakMap<ChunkFilterFunction, SelectedChunksResult>>} */
  378. const selectedChunksCacheByChunksSet = new WeakMap();
  379. /**
  380. * get list and key by applying the filter function to the list
  381. * It is cached for performance reasons
  382. * @param {Set<Chunk>} chunks list of chunks
  383. * @param {ChunkFilterFunction} chunkFilter filter function for chunks
  384. * @returns {SelectedChunksResult} list and key
  385. */
  386. const getSelectedChunks = (chunks, chunkFilter) => {
  387. let entry = selectedChunksCacheByChunksSet.get(chunks);
  388. if (entry === undefined) {
  389. entry = new WeakMap();
  390. selectedChunksCacheByChunksSet.set(chunks, entry);
  391. }
  392. /** @type {SelectedChunksResult} */
  393. let entry2 = entry.get(chunkFilter);
  394. if (entry2 === undefined) {
  395. /** @type {Chunk[]} */
  396. const selectedChunks = [];
  397. for (const chunk of chunks) {
  398. if (chunkFilter(chunk)) selectedChunks.push(chunk);
  399. }
  400. entry2 = {
  401. chunks: selectedChunks,
  402. key: getKey(selectedChunks)
  403. };
  404. entry.set(chunkFilter, entry2);
  405. }
  406. return entry2;
  407. };
  408. /**
  409. * @typedef {Object} ChunksInfoItem
  410. * @property {SortableSet} modules
  411. * @property {TODO} cacheGroup
  412. * @property {string} name
  413. * @property {number} size
  414. * @property {Set<Chunk>} chunks
  415. * @property {Set<Chunk>} reuseableChunks
  416. * @property {Set<string>} chunksKeys
  417. */
  418. // Map a list of chunks to a list of modules
  419. // For the key the chunk "index" is used, the value is a SortableSet of modules
  420. /** @type {Map<string, ChunksInfoItem>} */
  421. const chunksInfoMap = new Map();
  422. /**
  423. * @param {TODO} cacheGroup the current cache group
  424. * @param {Chunk[]} selectedChunks chunks selected for this module
  425. * @param {string} selectedChunksKey a key of selectedChunks
  426. * @param {Module} module the current module
  427. * @returns {void}
  428. */
  429. const addModuleToChunksInfoMap = (
  430. cacheGroup,
  431. selectedChunks,
  432. selectedChunksKey,
  433. module
  434. ) => {
  435. // Break if minimum number of chunks is not reached
  436. if (selectedChunks.length < cacheGroup.minChunks) return;
  437. // Determine name for split chunk
  438. const name = cacheGroup.getName(
  439. module,
  440. selectedChunks,
  441. cacheGroup.key
  442. );
  443. // Create key for maps
  444. // When it has a name we use the name as key
  445. // Elsewise we create the key from chunks and cache group key
  446. // This automatically merges equal names
  447. const key =
  448. (name && `name:${name}`) ||
  449. `chunks:${selectedChunksKey} key:${cacheGroup.key}`;
  450. // Add module to maps
  451. let info = chunksInfoMap.get(key);
  452. if (info === undefined) {
  453. chunksInfoMap.set(
  454. key,
  455. (info = {
  456. modules: new SortableSet(undefined, sortByIdentifier),
  457. cacheGroup,
  458. name,
  459. size: 0,
  460. chunks: new Set(),
  461. reuseableChunks: new Set(),
  462. chunksKeys: new Set()
  463. })
  464. );
  465. } else {
  466. if (info.cacheGroup !== cacheGroup) {
  467. if (info.cacheGroup.priority < cacheGroup.priority) {
  468. info.cacheGroup = cacheGroup;
  469. }
  470. }
  471. }
  472. info.modules.add(module);
  473. info.size += module.size();
  474. if (!info.chunksKeys.has(selectedChunksKey)) {
  475. info.chunksKeys.add(selectedChunksKey);
  476. for (const chunk of selectedChunks) {
  477. info.chunks.add(chunk);
  478. }
  479. }
  480. };
  481. // Walk through all modules
  482. for (const module of compilation.modules) {
  483. // Get cache group
  484. let cacheGroups = this.options.getCacheGroups(module);
  485. if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) {
  486. continue;
  487. }
  488. // Prepare some values
  489. const chunksKey = getKey(module.chunksIterable);
  490. let combs = combinationsCache.get(chunksKey);
  491. if (combs === undefined) {
  492. combs = getCombinations(chunksKey);
  493. combinationsCache.set(chunksKey, combs);
  494. }
  495. for (const cacheGroupSource of cacheGroups) {
  496. const cacheGroup = {
  497. key: cacheGroupSource.key,
  498. priority: cacheGroupSource.priority || 0,
  499. chunksFilter:
  500. cacheGroupSource.chunksFilter || this.options.chunksFilter,
  501. minSize:
  502. cacheGroupSource.minSize !== undefined
  503. ? cacheGroupSource.minSize
  504. : cacheGroupSource.enforce
  505. ? 0
  506. : this.options.minSize,
  507. maxSize:
  508. cacheGroupSource.maxSize !== undefined
  509. ? cacheGroupSource.maxSize
  510. : cacheGroupSource.enforce
  511. ? 0
  512. : this.options.maxSize,
  513. minChunks:
  514. cacheGroupSource.minChunks !== undefined
  515. ? cacheGroupSource.minChunks
  516. : cacheGroupSource.enforce
  517. ? 1
  518. : this.options.minChunks,
  519. maxAsyncRequests:
  520. cacheGroupSource.maxAsyncRequests !== undefined
  521. ? cacheGroupSource.maxAsyncRequests
  522. : cacheGroupSource.enforce
  523. ? Infinity
  524. : this.options.maxAsyncRequests,
  525. maxInitialRequests:
  526. cacheGroupSource.maxInitialRequests !== undefined
  527. ? cacheGroupSource.maxInitialRequests
  528. : cacheGroupSource.enforce
  529. ? Infinity
  530. : this.options.maxInitialRequests,
  531. getName:
  532. cacheGroupSource.getName !== undefined
  533. ? cacheGroupSource.getName
  534. : this.options.getName,
  535. filename:
  536. cacheGroupSource.filename !== undefined
  537. ? cacheGroupSource.filename
  538. : this.options.filename,
  539. automaticNameDelimiter:
  540. cacheGroupSource.automaticNameDelimiter !== undefined
  541. ? cacheGroupSource.automaticNameDelimiter
  542. : this.options.automaticNameDelimiter,
  543. reuseExistingChunk: cacheGroupSource.reuseExistingChunk
  544. };
  545. // For all combination of chunk selection
  546. for (const chunkCombination of combs) {
  547. // Break if minimum number of chunks is not reached
  548. if (chunkCombination.size < cacheGroup.minChunks) continue;
  549. // Select chunks by configuration
  550. const {
  551. chunks: selectedChunks,
  552. key: selectedChunksKey
  553. } = getSelectedChunks(
  554. chunkCombination,
  555. cacheGroup.chunksFilter
  556. );
  557. addModuleToChunksInfoMap(
  558. cacheGroup,
  559. selectedChunks,
  560. selectedChunksKey,
  561. module
  562. );
  563. }
  564. }
  565. }
  566. /** @type {Map<Chunk, {minSize: number, maxSize: number, automaticNameDelimiter: string}>} */
  567. const maxSizeQueueMap = new Map();
  568. while (chunksInfoMap.size > 0) {
  569. // Find best matching entry
  570. let bestEntryKey;
  571. let bestEntry;
  572. for (const pair of chunksInfoMap) {
  573. const key = pair[0];
  574. const info = pair[1];
  575. if (info.size >= info.cacheGroup.minSize) {
  576. if (bestEntry === undefined) {
  577. bestEntry = info;
  578. bestEntryKey = key;
  579. } else if (compareEntries(bestEntry, info) < 0) {
  580. bestEntry = info;
  581. bestEntryKey = key;
  582. }
  583. }
  584. }
  585. // No suitable item left
  586. if (bestEntry === undefined) break;
  587. const item = bestEntry;
  588. chunksInfoMap.delete(bestEntryKey);
  589. let chunkName = item.name;
  590. // Variable for the new chunk (lazy created)
  591. /** @type {Chunk} */
  592. let newChunk;
  593. // When no chunk name, check if we can reuse a chunk instead of creating a new one
  594. let isReused = false;
  595. if (item.cacheGroup.reuseExistingChunk) {
  596. outer: for (const chunk of item.chunks) {
  597. if (chunk.getNumberOfModules() !== item.modules.size) continue;
  598. if (chunk.hasEntryModule()) continue;
  599. for (const module of item.modules) {
  600. if (!chunk.containsModule(module)) continue outer;
  601. }
  602. if (!newChunk || !newChunk.name) {
  603. newChunk = chunk;
  604. } else if (
  605. chunk.name &&
  606. chunk.name.length < newChunk.name.length
  607. ) {
  608. newChunk = chunk;
  609. } else if (
  610. chunk.name &&
  611. chunk.name.length === newChunk.name.length &&
  612. chunk.name < newChunk.name
  613. ) {
  614. newChunk = chunk;
  615. }
  616. chunkName = undefined;
  617. isReused = true;
  618. }
  619. }
  620. // Check if maxRequests condition can be fulfilled
  621. const usedChunks = Array.from(item.chunks).filter(chunk => {
  622. // skip if we address ourself
  623. return (
  624. (!chunkName || chunk.name !== chunkName) && chunk !== newChunk
  625. );
  626. });
  627. // Skip when no chunk selected
  628. if (usedChunks.length === 0) continue;
  629. const chunkInLimit = usedChunks.filter(chunk => {
  630. // respect max requests when not enforced
  631. const maxRequests = chunk.isOnlyInitial()
  632. ? item.cacheGroup.maxInitialRequests
  633. : chunk.canBeInitial()
  634. ? Math.min(
  635. item.cacheGroup.maxInitialRequests,
  636. item.cacheGroup.maxAsyncRequests
  637. )
  638. : item.cacheGroup.maxAsyncRequests;
  639. return !isFinite(maxRequests) || getRequests(chunk) < maxRequests;
  640. });
  641. if (chunkInLimit.length < usedChunks.length) {
  642. for (const module of item.modules) {
  643. addModuleToChunksInfoMap(
  644. item.cacheGroup,
  645. chunkInLimit,
  646. getKey(chunkInLimit),
  647. module
  648. );
  649. }
  650. continue;
  651. }
  652. // Create the new chunk if not reusing one
  653. if (!isReused) {
  654. newChunk = compilation.addChunk(chunkName);
  655. }
  656. // Walk through all chunks
  657. for (const chunk of usedChunks) {
  658. // Add graph connections for splitted chunk
  659. chunk.split(newChunk);
  660. }
  661. // Add a note to the chunk
  662. newChunk.chunkReason = isReused
  663. ? "reused as split chunk"
  664. : "split chunk";
  665. if (item.cacheGroup.key) {
  666. newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`;
  667. }
  668. if (chunkName) {
  669. newChunk.chunkReason += ` (name: ${chunkName})`;
  670. // If the chosen name is already an entry point we remove the entry point
  671. const entrypoint = compilation.entrypoints.get(chunkName);
  672. if (entrypoint) {
  673. compilation.entrypoints.delete(chunkName);
  674. entrypoint.remove();
  675. newChunk.entryModule = undefined;
  676. }
  677. }
  678. if (item.cacheGroup.filename) {
  679. if (!newChunk.isOnlyInitial()) {
  680. throw new Error(
  681. "SplitChunksPlugin: You are trying to set a filename for a chunk which is (also) loaded on demand. " +
  682. "The runtime can only handle loading of chunks which match the chunkFilename schema. " +
  683. "Using a custom filename would fail at runtime. " +
  684. `(cache group: ${item.cacheGroup.key})`
  685. );
  686. }
  687. newChunk.filenameTemplate = item.cacheGroup.filename;
  688. }
  689. if (!isReused) {
  690. // Add all modules to the new chunk
  691. for (const module of item.modules) {
  692. if (typeof module.chunkCondition === "function") {
  693. if (!module.chunkCondition(newChunk)) continue;
  694. }
  695. // Add module to new chunk
  696. GraphHelpers.connectChunkAndModule(newChunk, module);
  697. // Remove module from used chunks
  698. for (const chunk of usedChunks) {
  699. chunk.removeModule(module);
  700. module.rewriteChunkInReasons(chunk, [newChunk]);
  701. }
  702. }
  703. } else {
  704. // Remove all modules from used chunks
  705. for (const module of item.modules) {
  706. for (const chunk of usedChunks) {
  707. chunk.removeModule(module);
  708. module.rewriteChunkInReasons(chunk, [newChunk]);
  709. }
  710. }
  711. }
  712. if (item.cacheGroup.maxSize > 0) {
  713. const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
  714. maxSizeQueueMap.set(newChunk, {
  715. minSize: Math.max(
  716. oldMaxSizeSettings ? oldMaxSizeSettings.minSize : 0,
  717. item.cacheGroup.minSize
  718. ),
  719. maxSize: Math.min(
  720. oldMaxSizeSettings ? oldMaxSizeSettings.maxSize : Infinity,
  721. item.cacheGroup.maxSize
  722. ),
  723. automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter
  724. });
  725. }
  726. // remove all modules from other entries and update size
  727. for (const [key, info] of chunksInfoMap) {
  728. if (isOverlap(info.chunks, item.chunks)) {
  729. const oldSize = info.modules.size;
  730. for (const module of item.modules) {
  731. info.modules.delete(module);
  732. }
  733. if (info.modules.size === 0) {
  734. chunksInfoMap.delete(key);
  735. continue;
  736. }
  737. if (info.modules.size !== oldSize) {
  738. info.size = getModulesSize(info.modules);
  739. if (info.size < info.cacheGroup.minSize) {
  740. chunksInfoMap.delete(key);
  741. }
  742. }
  743. }
  744. }
  745. }
  746. // Make sure that maxSize is fulfilled
  747. for (const chunk of compilation.chunks.slice()) {
  748. const { minSize, maxSize, automaticNameDelimiter } =
  749. maxSizeQueueMap.get(chunk) || this.options.fallbackCacheGroup;
  750. if (!maxSize) continue;
  751. const results = deterministicGroupingForModules({
  752. maxSize,
  753. minSize,
  754. items: chunk.modulesIterable,
  755. getKey(module) {
  756. const ident = contextify(
  757. compilation.options.context,
  758. module.identifier()
  759. );
  760. const name = module.nameForCondition
  761. ? contextify(
  762. compilation.options.context,
  763. module.nameForCondition()
  764. )
  765. : ident.replace(/^.*!|\?[^?!]*$/g, "");
  766. const fullKey =
  767. name + automaticNameDelimiter + hashFilename(ident);
  768. return fullKey.replace(/[\\/?]/g, "_");
  769. },
  770. getSize(module) {
  771. return module.size();
  772. }
  773. });
  774. results.sort((a, b) => {
  775. if (a.key < b.key) return -1;
  776. if (a.key > b.key) return 1;
  777. return 0;
  778. });
  779. for (let i = 0; i < results.length; i++) {
  780. const group = results[i];
  781. const key = this.options.hidePathInfo
  782. ? hashFilename(group.key)
  783. : group.key;
  784. let name = chunk.name
  785. ? chunk.name + automaticNameDelimiter + key
  786. : null;
  787. if (name && name.length > 100) {
  788. name =
  789. name.slice(0, 100) +
  790. automaticNameDelimiter +
  791. hashFilename(name);
  792. }
  793. let newPart;
  794. if (i !== results.length - 1) {
  795. newPart = compilation.addChunk(name);
  796. chunk.split(newPart);
  797. newPart.chunkReason = chunk.chunkReason;
  798. // Add all modules to the new chunk
  799. for (const module of group.items) {
  800. if (typeof module.chunkCondition === "function") {
  801. if (!module.chunkCondition(newPart)) continue;
  802. }
  803. // Add module to new chunk
  804. GraphHelpers.connectChunkAndModule(newPart, module);
  805. // Remove module from used chunks
  806. chunk.removeModule(module);
  807. module.rewriteChunkInReasons(chunk, [newPart]);
  808. }
  809. } else {
  810. // change the chunk to be a part
  811. newPart = chunk;
  812. chunk.name = name;
  813. }
  814. }
  815. }
  816. }
  817. );
  818. });
  819. }
  820. };