output.experimentalMinChunkSize
概述
experimentalMinChunkSize 设置一个最小 chunk 大小阈值(单位:字节),Rollup 会尝试将低于该阈值的自动 chunk 合并到其他 chunk 中,以减少输出文件数量。
// rollup.config.js
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'es',
experimentalMinChunkSize: 1000
}
};2
3
4
5
6
7
8
9
适用范围
- 仅对自动 chunk 生效,不会重写
manualChunks定义的 chunk。 - 仅在默认多 chunk 路径下运行(
inlineDynamicImports = false且preserveModules = false)。 - 在 chunk 划分算法的最后阶段执行,位于动态入口优化与重新聚类之后。
关于
experimentalMinChunkSize在整体 chunk 划分流程中的位置,参见 Chunk 划分算法。
可复现基线
- 源码基线(当前 Rollup 仓库提交):
c79e6c201d1f99e126d2e6bfb3f8c5c100ddcebf - 关键文件:
src/utils/chunkAssignment.ts
结论严格基于以上版本源码,不保证适用于其他版本或分支。
合并算法详解
MINCHUNK 合并流程
MERGE1) 算法概述
合并优化采用贪心算法,目标是减少小 chunk 数量,同时保证:
- 副作用不变式:合并后各入口的 correlated side effects 保持不变
- 无循环依赖:合并不会产生 chunk 间的循环依赖
- 最小化额外加载:优先选择引入最少额外代码的合并目标
入口函数 getOptimizedChunks 协调整个合并流程:
function getOptimizedChunks(
chunks: ChunkDescription[],
minChunkSize: number,
sideEffectAtoms: bigint,
sizeByAtom: number[],
log: LogHandler
): { modules: Module[] }[] {
timeStart('optimize chunks', 3);
const chunkPartition = getPartitionedChunks(chunks, minChunkSize);
if (!chunkPartition) {
timeEnd('optimize chunks', 3);
return chunks;
}
// ... logging
mergeChunks(chunkPartition, minChunkSize, sideEffectAtoms, sizeByAtom);
// ...
return [...chunkPartition.small, ...chunkPartition.big];
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 位置:
src/utils/chunkAssignment.ts:740-769
2) 核心数据结构
ChunkDescription 描述一个待合并的 chunk:
interface ChunkDescription extends ModulesWithDependentEntries {
containedAtoms: bigint; // 本 chunk 包含的原子(位掩码)
correlatedAtoms: bigint; // 加载本 chunk 时必然已加载的原子
dependencies: Set<ChunkDescription>;
dependentChunks: Set<ChunkDescription>;
pure: boolean; // 是否无副作用
size: number;
}2
3
4
5
6
7
8
ChunkPartition 将 chunk 分为大小两类:
interface ChunkPartition {
big: Set<ChunkDescription>; // size >= minChunkSize
small: Set<ChunkDescription>; // size < minChunkSize
}2
3
4
- 位置:
src/utils/chunkAssignment.ts:19-39
BigInt 位掩码:每个原子对应一个 bit 位置,例如 atom 0 = 1n,atom 1 = 2n,atom 2 = 4n。使用位运算进行高效集合操作。
3) 分区与排序
getPartitionedChunks 将 chunk 按 minChunkSize 阈值分为两类,并按 size 升序排列:
function getPartitionedChunks(
chunks: ChunkDescription[],
minChunkSize: number
): ChunkPartition | null {
const smallChunks: ChunkDescription[] = [];
const bigChunks: ChunkDescription[] = [];
for (const chunk of chunks) {
(chunk.size < minChunkSize ? smallChunks : bigChunks).push(chunk);
}
if (smallChunks.length === 0) {
return null; // 无小 chunk,跳过合并
}
smallChunks.sort(compareChunkSize);
bigChunks.sort(compareChunkSize);
return {
big: new Set(bigChunks),
small: new Set(smallChunks)
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 位置:
src/utils/chunkAssignment.ts:771-789
4) 贪心合并循环
mergeChunks 按 size 从小到大遍历小 chunk,为每个找最佳合并目标:
function mergeChunks(
chunkPartition: ChunkPartition,
minChunkSize: number,
sideEffectAtoms: bigint,
sizeByAtom: number[]
) {
const { small } = chunkPartition;
for (const mergedChunk of small) {
const bestTargetChunk = findBestMergeTarget(
mergedChunk,
chunkPartition,
sideEffectAtoms,
sizeByAtom,
minChunkSize <= 1 ? 1 : Infinity
);
if (bestTargetChunk) {
// 执行合并...
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
合并时的属性更新规则:
| 属性 | 更新方式 | 原因 |
|---|---|---|
modules | 拼接 | 合并所有模块 |
size | 求和 | 累加代码大小 |
pure | 逻辑与 | 只有双方都无副作用才保持 pure |
correlatedAtoms | 取交集 | 合并后必然已加载的原子更少 |
containedAtoms | 取并集 | 包含双方所有原子 |
dependentEntries | 取并集 | 合并依赖入口 |
关键:correlatedAtoms 取交集确保合并后副作用不变式仍然成立。
- 位置:
src/utils/chunkAssignment.ts:798-842
5) 副作用约束详解
合并的核心约束是不引入非关联副作用。在 getAdditionalSizeIfNoTransitiveDependencyOrNonCorrelatedSideEffect 中检查:
const { correlatedAtoms } = dependencyChunk;
let dependencyAtoms = dependentChunk.containedAtoms;
const dependentContainedSideEffects = dependencyAtoms & sideEffectAtoms;
if ((correlatedAtoms & dependentContainedSideEffects) !== dependentContainedSideEffects) {
return Infinity; // 合并会引入非关联副作用,拒绝
}2
3
4
5
6
数学表达式:
合并有效条件:
dependentChunk.containedSideEffects ⊆ dependencyChunk.correlatedAtoms
即位运算表示:
(containedAtoms & sideEffectAtoms) & ~correlatedAtoms === 0n2
3
4
5
解释:若 dependentChunk 包含的副作用不是 dependencyChunk 的 correlated side effects 的子集,则合并会导致某些入口加载时意外执行额外副作用,违反副作用不变式。
- 位置:
src/utils/chunkAssignment.ts:914-918
6) 循环检测机制
合并可能引入 chunk 间循环依赖,必须检测并拒绝。使用 BFS 遍历依赖图:
const chunksToCheck = new Set(dependentChunk.dependencies);
for (const { dependencies, containedAtoms } of chunksToCheck) {
dependencyAtoms |= containedAtoms;
const containedSideEffects = containedAtoms & sideEffectAtoms;
if ((correlatedAtoms & containedSideEffects) !== containedSideEffects) {
return Infinity; // 传递依赖包含非关联副作用
}
for (const dependency of dependencies) {
if (dependency === dependencyChunk) {
return Infinity; // 发现循环!
}
chunksToCheck.add(dependency);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
原理:若 dependentChunk 的传递依赖中包含 dependencyChunk,则合并后会形成循环:mergedChunk → dependencyChunk → ... → mergedChunk。
- 位置:
src/utils/chunkAssignment.ts:920-932
7) 大小计算与目标选择
getAdditionalSizeAfterMerge 双向检查合并成本:
function getAdditionalSizeAfterMerge(
mergedChunk: ChunkDescription,
targetChunk: ChunkDescription,
currentAdditionalSize: number,
sideEffectAtoms: bigint,
sizeByAtom: number[]
): number {
const firstSize = getAdditionalSizeIfNoTransitiveDependencyOrNonCorrelatedSideEffect(
mergedChunk, targetChunk, currentAdditionalSize, sideEffectAtoms, sizeByAtom
);
return firstSize < currentAdditionalSize
? firstSize + getAdditionalSizeIfNoTransitiveDependencyOrNonCorrelatedSideEffect(
targetChunk, mergedChunk, currentAdditionalSize - firstSize, sideEffectAtoms, sizeByAtom
)
: Infinity;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
额外大小计算:
return getAtomsSizeIfBelowLimit(
dependencyAtoms & ~correlatedAtoms, // 未被关联的原子
currentAdditionalSize,
sizeByAtom
);2
3
4
5
findBestMergeTarget 遍历所有候选,选择额外大小最小的目标:
for (const targetChunk of concatLazy([small, big])) {
if (mergedChunk === targetChunk) continue;
const additionalSizeAfterMerge = getAdditionalSizeAfterMerge(...);
if (additionalSizeAfterMerge < smallestAdditionalSize) {
bestTargetChunk = targetChunk;
if (additionalSizeAfterMerge === 0) break; // 提前退出优化
smallestAdditionalSize = additionalSizeAfterMerge;
}
}2
3
4
5
6
7
8
9
- 位置:
src/utils/chunkAssignment.ts:844-869, 879-939
重要边界条件
manualChunks 不受影响:
experimentalMinChunkSize仅对自动 chunk 生效,不会重写manualChunks定义的 chunk。preserveModules与inlineDynamicImports下不生效:两者会直接绕过默认分块算法,experimentalMinChunkSize合并不适用。副作用严格约束:即使 chunk 低于阈值,若合并会引入非关联副作用或循环依赖,该 chunk 仍保持独立。
默认行为(
minChunkSize为1):默认值为1,此时仅合并额外大小为0的 chunk(即合并不增加任何冗余代码)。