"use strict";
const validateOptions = require("schema-utils");
const schema = require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json");
const LazyBucketSortedSet = require("../util/LazyBucketSortedSet");
const addToSetMap = (map, key, value) => {
const set = map.get(key);
if (set === undefined) {
map.set(key, new Set([value]));
} else {
set.add(value);
}
};
class LimitChunkCountPlugin {
constructor(options) {
if (!options) options = {};
validateOptions(schema, options, "Limit Chunk Count Plugin");
this.options = options;
}
apply(compiler) {
const options = this.options;
compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => {
compilation.hooks.optimizeChunksAdvanced.tap(
"LimitChunkCountPlugin",
chunks => {
const maxChunks = options.maxChunks;
if (!maxChunks) return;
if (maxChunks < 1) return;
if (chunks.length <= maxChunks) return;
let remainingChunksToMerge = chunks.length - maxChunks;
const orderedChunks = chunks.slice().sort((a, b) => a.compareTo(b));
const combinations = new LazyBucketSortedSet(
c => c.sizeDiff,
(a, b) => b - a,
c => c.integratedSize,
(a, b) => a - b,
c => c.bIdx - c.aIdx,
(a, b) => a - b,
(a, b) => a.bIdx - b.bIdx
);
const combinationsByChunk = new Map();
orderedChunks.forEach((b, bIdx) => {
for (let aIdx = 0; aIdx < bIdx; aIdx++) {
const a = orderedChunks[aIdx];
const integratedSize = a.integratedSize(b, options);
if (integratedSize === false) continue;
const aSize = a.size(options);
const bSize = b.size(options);
const c = {
deleted: false,
sizeDiff: aSize + bSize - integratedSize,
integratedSize,
a,
b,
aIdx,
bIdx,
aSize,
bSize
};
combinations.add(c);
addToSetMap(combinationsByChunk, a, c);
addToSetMap(combinationsByChunk, b, c);
}
return combinations;
});
const modifiedChunks = new Set();
let changed = false;
loop: while (true) {
const combination = combinations.popFirst();
if (combination === undefined) break;
combination.deleted = true;
const { a, b, integratedSize } = combination;
if (modifiedChunks.size > 0) {
const queue = new Set(a.groupsIterable);
for (const group of b.groupsIterable) {
queue.add(group);
}
for (const group of queue) {
for (const mChunk of modifiedChunks) {
if (mChunk !== a && mChunk !== b && mChunk.isInGroup(group)) {
remainingChunksToMerge--;
if (remainingChunksToMerge <= 0) break loop;
modifiedChunks.add(a);
modifiedChunks.add(b);
continue loop;
}
}
for (const parent of group.parentsIterable) {
queue.add(parent);
}
}
}
if (a.integrate(b, "limit")) {
chunks.splice(chunks.indexOf(b), 1);
modifiedChunks.add(a);
changed = true;
remainingChunksToMerge--;
if (remainingChunksToMerge <= 0) break;
for (const combination of combinationsByChunk.get(b)) {
if (combination.deleted) continue;
combination.deleted = true;
combinations.delete(combination);
}
for (const combination of combinationsByChunk.get(a)) {
if (combination.deleted) continue;
if (combination.a === a) {
const newIntegratedSize = a.integratedSize(
combination.b,
options
);
if (newIntegratedSize === false) {
combination.deleted = true;
combinations.delete(combination);
continue;
}
const finishUpdate = combinations.startUpdate(combination);
combination.integratedSize = newIntegratedSize;
combination.aSize = integratedSize;
combination.sizeDiff =
combination.bSize + integratedSize - newIntegratedSize;
finishUpdate();
} else if (combination.b === a) {
const newIntegratedSize = combination.a.integratedSize(
a,
options
);
if (newIntegratedSize === false) {
combination.deleted = true;
combinations.delete(combination);
continue;
}
const finishUpdate = combinations.startUpdate(combination);
combination.integratedSize = newIntegratedSize;
combination.bSize = integratedSize;
combination.sizeDiff =
integratedSize + combination.aSize - newIntegratedSize;
finishUpdate();
}
}
}
}
if (changed) return true;
}
);
});
}
}
module.exports = LimitChunkCountPlugin;