Skip to content

Commit e4ceffb

Browse files
committed
fix(scripts): 懒侧上限加实测漂移容差
1 parent 1bce07d commit e4ceffb

1 file changed

Lines changed: 16 additions & 16 deletions

File tree

‎scripts/check-bundle-budget.mjs‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
* 🔴 **口径:本判据只在新鲜构建的产物上成立** —— 旧 `dist` 带着上一个时代的残渣(实测:顶层 katex 被删后旧
2121
* 产物仍多 **64,497 B** gzip)⇒ **`--no-build` 的读数不可用于本判据**(要判既有产物就显式 `--dist` 指认)。
2222
* 首屏与懒侧是**两个独立读数**:懒侧判定不得影响首屏字段。
23+
* 5. **容差(批 7 §C25.4)**:逐族与**总量**判据都用「上限 + `TOLERANCE`」,取 `max(1 × 同源构建漂移, 64 B)`。
24+
* 实测漂移两路并列:本单元**两棵同源树各构建一次 = 逐族与总量 0 B**(chunk 名集合逐字相同)· R-4 在 HEAD 上读 **−18 B**
25+
* ⇒ `max(1 × 18, 64)` = **64 B**。🔴 常量、不得随提交变大(改它 = 改判据);人类输出与 `--json` 都打印它。
2326
*
2427
* 仪器陷阱(本批实测,勿改回):
2528
* - 静态边在产物里长 `from"./x-abc.js"`:**引号必须紧跟** `from` / `import`,故 `import(` 天然不匹配;
@@ -64,6 +67,9 @@ const BUDGET_SOURCE = "docs/standards/performance.md:28";
6467
const LAZY_BUDGET = join(dirname(SELF), "lazyBudget.json");
6568
// 懒侧判据的**合格口径 = 新鲜构建**(§C21.3):① 不带 `--no-build` ⇒ 本次真构建;② 显式 `--dist` ⇒ 操作者把该产物作为受判对象交进来;两者都不满足(裸 `--no-build`)⇒ 只判首屏,懒侧**不下判定**。
6669
const JUDGE_LAZY = !NO_LAZY && (!NO_BUILD || has("--dist"));
70+
// 容差(§C25.4):`max(1 × 同源构建漂移, 64 B)`。漂移实测 = 本单元 0 B(两棵同源树)· R-4 −18 B ⇒ 取包住两者的 18 B ⇒ 64 B 下限生效。
71+
// 🔴 常量、**不得**随任何提交变大(改它 = 改判据,须回报控制方);`k = 1`,两个读数见文件头第 5 条。
72+
const TOLERANCE = Math.max(1 * 18, 64);
6773

6874
/** 全部失败路径的统一出口:**必须** exit 2(1 只留给「超预算」),并给出可执行的下一条命令。 */
6975
function fail(msg) {
@@ -97,14 +103,13 @@ function judgeLazy(r, base) {
97103
f.chunks.push(c.name);
98104
f.gzipBytes += c.gzipBytes;
99105
}
100-
const families = [...fams.values()];
101-
const totalBytes = r.lazy.reduce((a, c) => a + c.gzipBytes, 0);
106+
const families = [...fams.values()]; const totalBytes = r.lazy.reduce((a, c) => a + c.gzipBytes, 0);
102107
const reasons = [];
103108
if (unlisted.length) reasons.push(`① 族前缀清单等式:未归族 ${unlisted.length} 个(${unlisted.join(", ")})`);
104-
for (const f of families) if (f.gzipBytes > f.gzipBytesMax) reasons.push(`② 逐族上限:${f.prefix} gzip ${fmtB(f.gzipBytes)} B > 上限 ${fmtB(f.gzipBytesMax)} B`);
105-
if (totalBytes > base.lazyTotalGzipBytesMax) reasons.push(`③ 懒侧总 gzip:${fmtB(totalBytes)} B > 上限 ${fmtB(base.lazyTotalGzipBytesMax)} B`);
109+
for (const f of families) if (f.gzipBytes > f.gzipBytesMax + TOLERANCE) reasons.push(`② 逐族上限:${f.prefix} gzip ${fmtB(f.gzipBytes)} B > 上限 ${fmtB(f.gzipBytesMax)} B + 容差 ${TOLERANCE} B`);
110+
if (totalBytes > base.lazyTotalGzipBytesMax + TOLERANCE) reasons.push(`③ 懒侧总 gzip:${fmtB(totalBytes)} B > 上限 ${fmtB(base.lazyTotalGzipBytesMax)} B + 容差 ${TOLERANCE} B`);
106111
if (r.lazy.length > base.lazyChunkCountMax) reasons.push(`④ 懒侧 chunk 数:${r.lazy.length} > 上限 ${base.lazyChunkCountMax}`);
107-
return { families, unlisted, overFamilies: families.filter((f) => f.gzipBytes > f.gzipBytesMax), totalBytes, totalMax: base.lazyTotalGzipBytesMax, count: r.lazy.length, countMax: base.lazyChunkCountMax, reasons, pass: reasons.length === 0 };
112+
return { families, unlisted, overFamilies: families.filter((f) => f.gzipBytes > f.gzipBytesMax + TOLERANCE), totalBytes, totalMax: base.lazyTotalGzipBytesMax, tolerance: TOLERANCE, count: r.lazy.length, countMax: base.lazyChunkCountMax, reasons, pass: reasons.length === 0 };
108113
}
109114

110115
/** 参考项:HTML 用 modulepreload 声明但静态闭包没覆盖的 .js —— 值得人看一眼,不影响判定。 */
@@ -125,10 +130,9 @@ function printHuman(r, pass, lazy) {
125130
}
126131
console.log(` 首屏 JS 合计:原始 ${fmtB(r.rawBytes)} B · gzip ${fmtB(r.eagerBytes)} B = ${kB(r.eagerBytes)} kB`);
127132
console.log(` ⇒ 首屏预算 ${pass ? "✅ 达标" : "❌ 超标"}:${pass ? "余量" : "超出"} ${kB(Math.abs(BUDGET_KB * 1000 - r.eagerBytes))} kB${pass ? "" : `(${(r.eagerBytes / 1000 / BUDGET_KB).toFixed(2)}× 预算)`}`);
128-
const lazyBytes = r.lazy.reduce((a, c) => a + c.gzipBytes, 0);
129-
console.log(`懒加载 chunk(仅动态可达,独立于首屏预算):${r.lazy.length} 个 · gzip ${fmtB(lazyBytes)} B = ${kB(lazyBytes)} kB`);
133+
console.log(`懒加载 chunk(仅动态可达,独立于首屏预算):${r.lazy.length} 个 · gzip ${fmtB(r.lazy.reduce((a, c) => a + c.gzipBytes, 0))} B = ${kB(r.lazy.reduce((a, c) => a + c.gzipBytes, 0))} kB`);
130134
if (lazy) {
131-
console.log(` ⇒ 懒侧预算 ${lazy.pass ? "✅ 达标" : "❌ 超标"}:gzip ${kB(lazy.totalBytes)} kB / 上限 ${kB(lazy.totalMax)} kB · chunk ${lazy.count} 个 / 上限 ${lazy.countMax} 个 · 未归族 ${lazy.unlisted.length} 个 · 超上限族 ${lazy.overFamilies.length} 个(基线 ${rel(LAZY_BUDGET)},实测冻结值只许降)`);
135+
console.log(` ⇒ 懒侧预算 ${lazy.pass ? "✅ 达标" : "❌ 超标"}:gzip ${kB(lazy.totalBytes)} kB / 上限 ${kB(lazy.totalMax)} kB + 容差 ${lazy.tolerance} B · chunk ${lazy.count} 个 / 上限 ${lazy.countMax} 个 · 未归族 ${lazy.unlisted.length} 个 · 超上限族 ${lazy.overFamilies.length} 个(基线 ${rel(LAZY_BUDGET)},实测冻结值只许降;容差 = max(1 × 同源构建漂移, 64 B))`);
132136
} else console.log(` ⇒ 懒侧预算 ⏭ 未判(${NO_LAZY ? "--no-lazy:自检夹具" : "--no-build 的读数不可用于本判据(旧产物会多算 ~64 kB)—— 去掉 --no-build 让本脚本代跑构建,或显式 --dist 指认受判产物"})`);
133137
console.log(`不计入预算但须报告(CSS / 字体):index.html ${fmtB(r.htmlBytes)} B = ${kB(r.htmlBytes)} kB · gzip ${kB(r.htmlGzip)} kB`);
134138
const byExt = new Map();
@@ -158,6 +162,7 @@ const jsonOf = (r, pass, lazy) => ({
158162
? {
159163
judged: true,
160164
pass: lazy.pass,
165+
tolerance: lazy.tolerance,
161166
baseline: rel(LAZY_BUDGET),
162167
count: lazy.count,
163168
countMax: lazy.countMax,
@@ -166,9 +171,9 @@ const jsonOf = (r, pass, lazy) => ({
166171
totalMaxBytes: lazy.totalMax,
167172
unlisted: lazy.unlisted,
168173
fails: lazy.reasons,
169-
families: lazy.families.map((f) => ({ prefix: f.prefix, chunkCount: f.chunks.length, gzipBytes: f.gzipBytes, gzipBytesMax: f.gzipBytesMax, pass: f.gzipBytes <= f.gzipBytesMax })),
174+
families: lazy.families.map((f) => ({ prefix: f.prefix, chunkCount: f.chunks.length, gzipBytes: f.gzipBytes, gzipBytesMax: f.gzipBytesMax, pass: f.gzipBytes <= f.gzipBytesMax + lazy.tolerance })),
170175
}
171-
: { judged: false, pass: null, skipped: NO_LAZY ? "--no-lazy(自检夹具)" : "--no-build 的读数不可用于懒侧判据(口径要求新鲜构建;亦可在显式 --dist 下判定)" },
176+
: { judged: false, pass: null, tolerance: TOLERANCE, skipped: NO_LAZY ? "--no-lazy(自检夹具)" : "--no-build 的读数不可用于懒侧判据(口径要求新鲜构建;亦可在显式 --dist 下判定)" },
172177
excludedFromBudget: { indexHtml: { bytes: r.htmlBytes, gzipBytes: r.htmlGzip }, assets: r.excluded },
173178
pass,
174179
});
@@ -187,12 +192,7 @@ function finish(r) {
187192
const uncovered = preloadUncovered(r.dist, r.eager);
188193
if (uncovered.length) console.log(`ℹ️ modulepreload 声明但静态闭包未覆盖 ${uncovered.length} 个:${uncovered.join(", ")}(参考项,不影响判定)`);
189194
}
190-
if (!pass) {
191-
console.error(
192-
`❌ 首屏 JS gzip ${fmtB(r.eagerBytes)} B = ${kB(r.eagerBytes)} kB ≥ 预算 ${BUDGET_KB} kB` +
193-
`(${(r.eagerBytes / 1000 / BUDGET_KB).toFixed(2)}×)—— 退出码 1。降首屏只能靠 import() 切断静态边;manualChunks 只切文件、一字节不降。`,
194-
);
195-
}
195+
if (!pass) console.error(`❌ 首屏 JS gzip ${fmtB(r.eagerBytes)} B = ${kB(r.eagerBytes)} kB ≥ 预算 ${BUDGET_KB} kB` + `(${(r.eagerBytes / 1000 / BUDGET_KB).toFixed(2)}×)—— 退出码 1。降首屏只能靠 import() 切断静态边;manualChunks 只切文件、一字节不降。`);
196196
if (lazy && !lazy.pass) console.error(`❌ 懒侧 gzip 预算超标(退出码 1)—— 具名断言:${lazy.reasons.join(";")}。基线 ${rel(LAZY_BUDGET)} 只许降(§C9.6 第 2 条)。`);
197197
process.exit(pass && (!lazy || lazy.pass) ? 0 : 1);
198198
}

0 commit comments

Comments
 (0)