277 lines
14 KiB
JavaScript
277 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "CssChunkingPlugin", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return CssChunkingPlugin;
|
|
}
|
|
});
|
|
const PLUGIN_NAME = "CssChunkingPlugin";
|
|
/**
|
|
* Merge chunks until they are bigger than the target size.
|
|
*/ const MIN_CSS_CHUNK_SIZE = 30 * 1024;
|
|
/**
|
|
* Avoid merging chunks when they would be bigger than this size.
|
|
*/ const MAX_CSS_CHUNK_SIZE = 100 * 1024;
|
|
function isGlobalCss(module) {
|
|
return !/\.module\.(css|scss|sass)$/.test(module.nameForCondition() || "");
|
|
}
|
|
class CssChunkingPlugin {
|
|
constructor(strict){
|
|
this.strict = strict;
|
|
}
|
|
apply(compiler) {
|
|
const strict = this.strict;
|
|
const summary = !!process.env.CSS_CHUNKING_SUMMARY;
|
|
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation)=>{
|
|
let once = false;
|
|
compilation.hooks.optimizeChunks.tap({
|
|
name: PLUGIN_NAME,
|
|
stage: 5
|
|
}, ()=>{
|
|
if (once) {
|
|
return;
|
|
}
|
|
once = true;
|
|
const chunkGraph = compilation.chunkGraph;
|
|
let changed = undefined;
|
|
const chunkStates = new Map();
|
|
const chunkStatesByModule = new Map();
|
|
// Collect all css modules in chunks and the execpted order of them
|
|
for (const chunk of compilation.chunks){
|
|
var _chunk_name;
|
|
if ((_chunk_name = chunk.name) == null ? void 0 : _chunk_name.startsWith("pages/")) continue;
|
|
const modules = [];
|
|
for (const module of chunkGraph.getChunkModulesIterable(chunk)){
|
|
var _module_type;
|
|
if (!((_module_type = module.type) == null ? void 0 : _module_type.startsWith("css"))) continue;
|
|
modules.push(module);
|
|
}
|
|
if (!modules.length) continue;
|
|
const chunkState = {
|
|
chunk,
|
|
modules,
|
|
order: 0,
|
|
requests: modules.length
|
|
};
|
|
chunkStates.set(chunk, chunkState);
|
|
for(let i = 0; i < modules.length; i++){
|
|
const module = modules[i];
|
|
let moduleChunkStates = chunkStatesByModule.get(module);
|
|
if (!moduleChunkStates) {
|
|
moduleChunkStates = new Map();
|
|
chunkStatesByModule.set(module, moduleChunkStates);
|
|
}
|
|
moduleChunkStates.set(chunkState, i);
|
|
chunkStatesByModule.set(module, moduleChunkStates);
|
|
}
|
|
}
|
|
// Sort modules by their index sum
|
|
const orderedModules = [];
|
|
for (const [module, moduleChunkStates] of chunkStatesByModule){
|
|
let sum = 0;
|
|
for (const i of moduleChunkStates.values()){
|
|
sum += i;
|
|
}
|
|
orderedModules.push({
|
|
module,
|
|
sum
|
|
});
|
|
}
|
|
orderedModules.sort((a, b)=>a.sum - b.sum);
|
|
// A queue of modules that still need to be processed
|
|
const remainingModules = new Set(orderedModules.map(({ module })=>module));
|
|
// In loose mode we guess the dependents of modules from the order
|
|
// assuming that when a module is a dependency of another module
|
|
// it will always appear before it in every chunk.
|
|
const allDependents = new Map();
|
|
if (!this.strict) {
|
|
for (const b of remainingModules){
|
|
const dependent = new Set();
|
|
loop: for (const a of remainingModules){
|
|
if (a === b) continue;
|
|
// check if a depends on b
|
|
for (const [chunkState, ia] of chunkStatesByModule.get(a)){
|
|
const bChunkStates = chunkStatesByModule.get(b);
|
|
const ib = bChunkStates.get(chunkState);
|
|
if (ib === undefined) {
|
|
continue loop;
|
|
}
|
|
if (ib > ia) {
|
|
continue loop;
|
|
}
|
|
}
|
|
dependent.add(a);
|
|
}
|
|
if (dependent.size > 0) allDependents.set(b, dependent);
|
|
}
|
|
}
|
|
// Stores the new chunk for every module
|
|
const newChunksByModule = new Map();
|
|
// Process through all modules
|
|
for (const startModule of remainingModules){
|
|
let globalCssMode = isGlobalCss(startModule);
|
|
// The current position of processing in all selected chunks
|
|
let allChunkStates = new Map(chunkStatesByModule.get(startModule));
|
|
// The list of modules that goes into the new chunk
|
|
const newChunkModules = new Set([
|
|
startModule
|
|
]);
|
|
// The current size of the new chunk
|
|
let currentSize = startModule.size();
|
|
// A pool of potential modules where the next module is selected from.
|
|
// It's filled from the next module of the selected modules in every chunk.
|
|
// It also keeps some metadata to improve performance [size, chunkStates].
|
|
const potentialNextModules = new Map();
|
|
for (const [chunkState, i] of allChunkStates){
|
|
const nextModule = chunkState.modules[i + 1];
|
|
if (nextModule && remainingModules.has(nextModule)) {
|
|
potentialNextModules.set(nextModule, [
|
|
nextModule.size(),
|
|
chunkStatesByModule.get(nextModule)
|
|
]);
|
|
}
|
|
}
|
|
// Try to add modules to the chunk until a break condition is met
|
|
let cont;
|
|
do {
|
|
cont = false;
|
|
// We try to select a module that reduces request count and
|
|
// has the highest number of requests
|
|
const orderedPotentialNextModules = [];
|
|
for (const [nextModule, [size, nextChunkStates]] of potentialNextModules){
|
|
let maxRequests = 0;
|
|
for (const chunkState of nextChunkStates.keys()){
|
|
// There is always some overlap
|
|
if (allChunkStates.has(chunkState)) {
|
|
maxRequests = Math.max(maxRequests, chunkState.requests);
|
|
}
|
|
}
|
|
orderedPotentialNextModules.push([
|
|
nextModule,
|
|
size,
|
|
nextChunkStates,
|
|
maxRequests
|
|
]);
|
|
}
|
|
orderedPotentialNextModules.sort((a, b)=>b[3] - a[3] || (a[0].identifier() < b[0].identifier() ? -1 : 1));
|
|
// Try every potential module
|
|
loop: for (const [nextModule, size, nextChunkStates] of orderedPotentialNextModules){
|
|
if (currentSize + size > MAX_CSS_CHUNK_SIZE) {
|
|
continue;
|
|
}
|
|
if (!strict) {
|
|
// In loose mode we only check if the dependencies are not violated
|
|
const dependent = allDependents.get(nextModule);
|
|
if (dependent) {
|
|
for (const dep of dependent){
|
|
if (newChunkModules.has(dep)) {
|
|
continue loop;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// In strict mode we check that none of the order in any chunk is changed by adding the module
|
|
for (const [chunkState, i] of nextChunkStates){
|
|
const prevState = allChunkStates.get(chunkState);
|
|
if (prevState === undefined) {
|
|
// New chunk group, can add it, but should we?
|
|
// We only add that if below min size
|
|
if (currentSize < MIN_CSS_CHUNK_SIZE) {
|
|
continue;
|
|
} else {
|
|
continue loop;
|
|
}
|
|
} else if (prevState + 1 === i) {
|
|
continue;
|
|
} else {
|
|
continue loop;
|
|
}
|
|
}
|
|
}
|
|
// Global CSS must not leak into unrelated chunks
|
|
const nextIsGlobalCss = isGlobalCss(nextModule);
|
|
if (nextIsGlobalCss && globalCssMode) {
|
|
if (allChunkStates.size !== nextChunkStates.size) {
|
|
continue;
|
|
}
|
|
}
|
|
if (globalCssMode) {
|
|
for (const chunkState of nextChunkStates.keys()){
|
|
if (!allChunkStates.has(chunkState)) {
|
|
continue loop;
|
|
}
|
|
}
|
|
}
|
|
if (nextIsGlobalCss) {
|
|
for (const chunkState of allChunkStates.keys()){
|
|
if (!nextChunkStates.has(chunkState)) {
|
|
continue loop;
|
|
}
|
|
}
|
|
}
|
|
potentialNextModules.delete(nextModule);
|
|
currentSize += size;
|
|
if (nextIsGlobalCss) {
|
|
globalCssMode = true;
|
|
}
|
|
for (const [chunkState, i] of nextChunkStates){
|
|
if (allChunkStates.has(chunkState)) {
|
|
// This reduces the request count of the chunk group
|
|
chunkState.requests--;
|
|
}
|
|
allChunkStates.set(chunkState, i);
|
|
const newNextModule = chunkState.modules[i + 1];
|
|
if (newNextModule && remainingModules.has(newNextModule) && !newChunkModules.has(newNextModule)) {
|
|
potentialNextModules.set(newNextModule, [
|
|
newNextModule.size(),
|
|
chunkStatesByModule.get(newNextModule)
|
|
]);
|
|
}
|
|
}
|
|
newChunkModules.add(nextModule);
|
|
cont = true;
|
|
break;
|
|
}
|
|
}while (cont);
|
|
const newChunk = compilation.addChunk();
|
|
newChunk.preventIntegration = true;
|
|
newChunk.idNameHints.add("css");
|
|
for (const module of newChunkModules){
|
|
remainingModules.delete(module);
|
|
chunkGraph.connectChunkAndModule(newChunk, module);
|
|
newChunksByModule.set(module, newChunk);
|
|
}
|
|
changed = true;
|
|
}
|
|
for (const { chunk, modules } of chunkStates.values()){
|
|
const chunks = new Set();
|
|
for (const module of modules){
|
|
const newChunk = newChunksByModule.get(module);
|
|
if (newChunk) {
|
|
chunkGraph.disconnectChunkAndModule(chunk, module);
|
|
if (chunks.has(newChunk)) continue;
|
|
chunks.add(newChunk);
|
|
chunk.split(newChunk);
|
|
}
|
|
}
|
|
}
|
|
if (summary) {
|
|
console.log("Top 20 chunks by request count:");
|
|
const orderedChunkStates = [
|
|
...chunkStates.values()
|
|
];
|
|
orderedChunkStates.sort((a, b)=>b.requests - a.requests);
|
|
for (const { chunk, modules, requests } of orderedChunkStates.slice(0, 20)){
|
|
console.log(`- ${requests} requests for ${chunk.name} (has ${modules.length} modules)`);
|
|
}
|
|
}
|
|
return changed;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
//# sourceMappingURL=css-chunking-plugin.js.map
|