source: frontend/node_modules/webpack/lib/optimize/LimitChunkCountPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 9.7 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { STAGE_ADVANCED } = require("../OptimizationStages");
9const LazyBucketSortedSet = require("../util/LazyBucketSortedSet");
10const { compareChunks } = require("../util/comparators");
11
12/** @typedef {import("../../declarations/plugins/optimize/LimitChunkCountPlugin").LimitChunkCountPluginOptions} LimitChunkCountPluginOptions */
13/** @typedef {import("../Chunk")} Chunk */
14/** @typedef {import("../Compiler")} Compiler */
15
16/**
17 * Defines the chunk combination type used by this module.
18 * @typedef {object} ChunkCombination
19 * @property {boolean} deleted this is set to true when combination was removed
20 * @property {number} sizeDiff
21 * @property {number} integratedSize
22 * @property {Chunk} a
23 * @property {Chunk} b
24 * @property {number} aIdx
25 * @property {number} bIdx
26 * @property {number} aSize
27 * @property {number} bSize
28 */
29
30/**
31 * Adds the provided map to this object.
32 * @template K, V
33 * @param {Map<K, Set<V>>} map map
34 * @param {K} key key
35 * @param {V} value value
36 */
37const addToSetMap = (map, key, value) => {
38 const set = map.get(key);
39 if (set === undefined) {
40 map.set(key, new Set([value]));
41 } else {
42 set.add(value);
43 }
44};
45
46const PLUGIN_NAME = "LimitChunkCountPlugin";
47
48class LimitChunkCountPlugin {
49 /**
50 * Creates an instance of LimitChunkCountPlugin.
51 * @param {LimitChunkCountPluginOptions=} options options object
52 */
53 constructor(options = { maxChunks: 1 }) {
54 /** @type {LimitChunkCountPluginOptions} */
55 this.options = options;
56 }
57
58 /**
59 * Applies the plugin by registering its hooks on the compiler.
60 * @param {Compiler} compiler the webpack compiler
61 * @returns {void}
62 */
63 apply(compiler) {
64 compiler.hooks.validate.tap(PLUGIN_NAME, () => {
65 compiler.validate(
66 () =>
67 require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json"),
68 this.options,
69 {
70 name: "Limit Chunk Count Plugin",
71 baseDataPath: "options"
72 },
73 (options) =>
74 require("../../schemas/plugins/optimize/LimitChunkCountPlugin.check")(
75 options
76 )
77 );
78 });
79
80 compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
81 compilation.hooks.optimizeChunks.tap(
82 {
83 name: PLUGIN_NAME,
84 stage: STAGE_ADVANCED
85 },
86 (chunks) => {
87 const chunkGraph = compilation.chunkGraph;
88 const maxChunks = this.options.maxChunks;
89 if (!maxChunks) return;
90 if (maxChunks < 1) return;
91 if (compilation.chunks.size <= maxChunks) return;
92
93 let remainingChunksToMerge = compilation.chunks.size - maxChunks;
94
95 // order chunks in a deterministic way
96 const compareChunksWithGraph = compareChunks(chunkGraph);
97 /** @type {Chunk[]} */
98 const orderedChunks = [...chunks].sort(compareChunksWithGraph);
99
100 // create a lazy sorted data structure to keep all combinations
101 // this is large. Size = chunks * (chunks - 1) / 2
102 // It uses a multi layer bucket sort plus normal sort in the last layer
103 // It's also lazy so only accessed buckets are sorted
104 /** @type {LazyBucketSortedSet<ChunkCombination, number>} */
105 const combinations = new LazyBucketSortedSet(
106 // Layer 1: ordered by largest size benefit
107 (c) => c.sizeDiff,
108 (a, b) => b - a,
109
110 // Layer 2: ordered by smallest combined size
111 /**
112 * Handles the stage callback for this hook.
113 * @param {ChunkCombination} c combination
114 * @returns {number} integrated size
115 */
116 (c) => c.integratedSize,
117 /**
118 * Handles the callback logic for this hook.
119 * @param {number} a a
120 * @param {number} b b
121 * @returns {number} result
122 */
123 (a, b) => a - b,
124
125 // Layer 3: ordered by position difference in orderedChunk (-> to be deterministic)
126 /**
127 * Handles the callback logic for this hook.
128 * @param {ChunkCombination} c combination
129 * @returns {number} position difference
130 */
131 (c) => c.bIdx - c.aIdx,
132 /**
133 * Handles the callback logic for this hook.
134 * @param {number} a a
135 * @param {number} b b
136 * @returns {number} result
137 */
138 (a, b) => a - b,
139
140 // Layer 4: ordered by position in orderedChunk (-> to be deterministic)
141 /**
142 * Handles the callback logic for this hook.
143 * @param {ChunkCombination} a a
144 * @param {ChunkCombination} b b
145 * @returns {number} result
146 */
147 (a, b) => a.bIdx - b.bIdx
148 );
149
150 // we keep a mapping from chunk to all combinations
151 // but this mapping is not kept up-to-date with deletions
152 // so `deleted` flag need to be considered when iterating this
153 /** @type {Map<Chunk, Set<ChunkCombination>>} */
154 const combinationsByChunk = new Map();
155
156 for (const [bIdx, b] of orderedChunks.entries()) {
157 // create combination pairs with size and integrated size
158 for (let aIdx = 0; aIdx < bIdx; aIdx++) {
159 const a = orderedChunks[aIdx];
160 // filter pairs that can not be integrated!
161 if (!chunkGraph.canChunksBeIntegrated(a, b)) continue;
162
163 const integratedSize = chunkGraph.getIntegratedChunksSize(
164 a,
165 b,
166 this.options
167 );
168
169 const aSize = chunkGraph.getChunkSize(a, this.options);
170 const bSize = chunkGraph.getChunkSize(b, this.options);
171 /** @type {ChunkCombination} */
172 const c = {
173 deleted: false,
174 sizeDiff: aSize + bSize - integratedSize,
175 integratedSize,
176 a,
177 b,
178 aIdx,
179 bIdx,
180 aSize,
181 bSize
182 };
183 combinations.add(c);
184 addToSetMap(combinationsByChunk, a, c);
185 addToSetMap(combinationsByChunk, b, c);
186 }
187 }
188
189 // list of modified chunks during this run
190 // combinations affected by this change are skipped to allow
191 // further optimizations
192 /** @type {Set<Chunk>} */
193 const modifiedChunks = new Set();
194
195 let changed = false;
196 loop: while (true) {
197 const combination = combinations.popFirst();
198 if (combination === undefined) break;
199
200 combination.deleted = true;
201 const { a, b, integratedSize } = combination;
202
203 // skip over pair when
204 // one of the already merged chunks is a parent of one of the chunks
205 if (modifiedChunks.size > 0) {
206 const queue = new Set(a.groupsIterable);
207 for (const group of b.groupsIterable) {
208 queue.add(group);
209 }
210 for (const group of queue) {
211 for (const mChunk of modifiedChunks) {
212 if (mChunk !== a && mChunk !== b && mChunk.isInGroup(group)) {
213 // This is a potential pair which needs recalculation
214 // We can't do that now, but it merge before following pairs
215 // so we leave space for it, and consider chunks as modified
216 // just for the worse case
217 remainingChunksToMerge--;
218 if (remainingChunksToMerge <= 0) break loop;
219 modifiedChunks.add(a);
220 modifiedChunks.add(b);
221 continue loop;
222 }
223 }
224 for (const parent of group.parentsIterable) {
225 queue.add(parent);
226 }
227 }
228 }
229
230 // merge the chunks
231 if (chunkGraph.canChunksBeIntegrated(a, b)) {
232 chunkGraph.integrateChunks(a, b);
233 compilation.chunks.delete(b);
234
235 // flag chunk a as modified as further optimization are possible for all children here
236 modifiedChunks.add(a);
237
238 changed = true;
239 remainingChunksToMerge--;
240 if (remainingChunksToMerge <= 0) break;
241
242 // Update all affected combinations
243 // delete all combination with the removed chunk
244 // we will use combinations with the kept chunk instead
245 for (const combination of /** @type {Set<ChunkCombination>} */ (
246 combinationsByChunk.get(a)
247 )) {
248 if (combination.deleted) continue;
249 combination.deleted = true;
250 combinations.delete(combination);
251 }
252
253 // Update combinations with the kept chunk with new sizes
254 for (const combination of /** @type {Set<ChunkCombination>} */ (
255 combinationsByChunk.get(b)
256 )) {
257 if (combination.deleted) continue;
258 if (combination.a === b) {
259 if (!chunkGraph.canChunksBeIntegrated(a, combination.b)) {
260 combination.deleted = true;
261 combinations.delete(combination);
262 continue;
263 }
264 // Update size
265 const newIntegratedSize = chunkGraph.getIntegratedChunksSize(
266 a,
267 combination.b,
268 this.options
269 );
270 const finishUpdate = combinations.startUpdate(combination);
271 combination.a = a;
272 combination.integratedSize = newIntegratedSize;
273 combination.aSize = integratedSize;
274 combination.sizeDiff =
275 combination.bSize + integratedSize - newIntegratedSize;
276 finishUpdate();
277 } else if (combination.b === b) {
278 if (!chunkGraph.canChunksBeIntegrated(combination.a, a)) {
279 combination.deleted = true;
280 combinations.delete(combination);
281 continue;
282 }
283 // Update size
284 const newIntegratedSize = chunkGraph.getIntegratedChunksSize(
285 combination.a,
286 a,
287 this.options
288 );
289
290 const finishUpdate = combinations.startUpdate(combination);
291 combination.b = a;
292 combination.integratedSize = newIntegratedSize;
293 combination.bSize = integratedSize;
294 combination.sizeDiff =
295 integratedSize + combination.aSize - newIntegratedSize;
296 finishUpdate();
297 }
298 }
299 combinationsByChunk.set(
300 a,
301 /** @type {Set<ChunkCombination>} */ (
302 combinationsByChunk.get(b)
303 )
304 );
305 combinationsByChunk.delete(b);
306 }
307 }
308 if (changed) return true;
309 }
310 );
311 });
312 }
313}
314
315module.exports = LimitChunkCountPlugin;
Note: See TracBrowser for help on using the repository browser.