add plugin to avoid too many css requests (#62530)
### What? Reduces the number of css chunks per entrypoint by merging chunks. ### Why? In larger application the total CSS deduplication approach falls short in a sense of it's causing way too many separate chunks, which results in way to many requests. That affects page load performance. Closes PACK-2598
This commit is contained in:
parent
d9b2a4baea
commit
110fc47f7c
2 changed files with 82 additions and 0 deletions
|
@ -83,6 +83,7 @@ import {
|
|||
createAppRouterApiAliases,
|
||||
} from './create-compiler-aliases'
|
||||
import { hasCustomExportOutput } from '../export/utils'
|
||||
import { MergeCssChunksPlugin } from './webpack/plugins/merge-css-chunks-plugin'
|
||||
|
||||
type ExcludesFalse = <T>(x: T | false) => x is T
|
||||
type ClientEntries = {
|
||||
|
@ -1877,6 +1878,7 @@ export default async function getBaseWebpackConfig(
|
|||
new NextFontManifestPlugin({
|
||||
appDir,
|
||||
}),
|
||||
!dev && isClient && new MergeCssChunksPlugin(),
|
||||
!dev &&
|
||||
isClient &&
|
||||
new (require('./webpack/plugins/telemetry-plugin').TelemetryPlugin)(
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import type { Chunk, ChunkGraph, Compiler } from 'webpack'
|
||||
|
||||
const PLUGIN_NAME = 'MergeCssChunksPlugin'
|
||||
|
||||
/**
|
||||
* Css chunks smaller than this size will be merged with other css chunks.
|
||||
*/
|
||||
const MIN_CSS_CHUNK_SIZE = 30 * 1024
|
||||
/**
|
||||
* When merging css chunks it will select an number N where the total size is just bigger than this size.
|
||||
* Exception: N must be at least 2 even if the size is already bigger than this size.
|
||||
*/
|
||||
const TARGET_CSS_CHUNK_SIZE = 60 * 1024
|
||||
/**
|
||||
* When an entrypoint has more css chunks than this number it merge the smallest ones to try to stay below that number.
|
||||
* Exception: When there are more than twice as much css chunks that larger than MAX_CSS_CHUNK_SIZE it will only half the number of css chunks.
|
||||
*/
|
||||
const MAX_CSS_CHUNKS = 15
|
||||
|
||||
function isCssChunk(chunkGraph: ChunkGraph, chunk: Chunk): boolean {
|
||||
for (const mod of chunkGraph.getChunkModulesIterable(chunk)) {
|
||||
return mod.type.startsWith('css')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export class MergeCssChunksPlugin {
|
||||
public apply(compiler: Compiler) {
|
||||
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
||||
let once = false
|
||||
compilation.hooks.optimizeChunks.tap(
|
||||
{
|
||||
name: PLUGIN_NAME,
|
||||
stage: 20,
|
||||
},
|
||||
() => {
|
||||
if (once) {
|
||||
return
|
||||
}
|
||||
once = true
|
||||
const chunkGraph = compilation.chunkGraph
|
||||
let changed = false
|
||||
for (const [, entrypoint] of compilation.entrypoints) {
|
||||
const cssChunks = entrypoint.chunks
|
||||
.filter((chunk) => isCssChunk(chunkGraph, chunk))
|
||||
.map((chunk) => [chunk, chunkGraph.getChunkSize(chunk)] as const)
|
||||
cssChunks.sort((a, b) => b[1] - a[1])
|
||||
|
||||
// We want have at most MAX_CSS_CHUNKS chunks.
|
||||
// When we start merging at startMergingIndex this would half the number of chunks after that index.
|
||||
const startMergingIndex = 2 * MAX_CSS_CHUNKS - cssChunks.length
|
||||
|
||||
// We select small chunks and chunks after the index
|
||||
const selectedCssChunks = cssChunks.filter(
|
||||
([, size], i) =>
|
||||
size < MIN_CSS_CHUNK_SIZE || i >= startMergingIndex
|
||||
)
|
||||
while (selectedCssChunks.length >= 2) {
|
||||
const [biggest, bigSize] = selectedCssChunks.shift()!
|
||||
let mergedSize = bigSize
|
||||
do {
|
||||
const [smallest, size] = selectedCssChunks.pop()!
|
||||
if (chunkGraph.canChunksBeIntegrated(biggest, smallest)) {
|
||||
chunkGraph.integrateChunks(biggest, smallest)
|
||||
compilation.chunks.delete(smallest)
|
||||
mergedSize += size
|
||||
changed = true
|
||||
}
|
||||
} while (
|
||||
selectedCssChunks.length > 0 &&
|
||||
mergedSize < TARGET_CSS_CHUNK_SIZE
|
||||
)
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue