微信小程序分包悄悄越线:亚稳态累积炸掉全员提交
某天早上,团队成员突然反馈「提交失败了」。报错信息清晰无误:
Error: 系统错误,错误码:80058, preloadRule [2] source size 2126KB exceed max limit 2MB
但怪的是——前几天还能提交,昨天也没有任何大改动,只是一次普通的 UI 细节优化。排查了半小时,真正的根因令人后背发凉:这个分包已经在 2040KB 附近「稳定」了两个多星期。
现象
- 突然全员提交报
source size NNNKB exceed max limit 2MB - 报错出现之前没有任何大功能上线,只有若干 UI polish 和小 refactor
- 回溯 git log,没有哪一个单独的 commit 显得「理所当然地超限」
- 关键词
preloadRule指向某个预加载分包,不是主包
根因
这是一个亚稳态累积问题,不是某次单点失误。
分包体积长期停在距 2MB 上限不足 10KB 的区间时,系统进入了一种危险的平衡态:
- 每次 PR 新增几 KB(图标、组件、样式),单独看都「合理」
- Code Review 时没有人盯整体体积,Reviewer 不会去计算「再加 8KB 就炸了」
- 这种状态可以持续数周,直到某次中等体量的 commit(比如一次 UI polish +24KB)把它推过线
- 推过线的那一刻,全部正在提交的人同时被阻断
一开始用 du -sk <分包目录> 统计源码大小并设置阈值告警。这个方案有根本性缺陷:
du -sk 统计的是源码原始大小,不是微信实际打包体积。
微信打包会对 JS 做 minify + tree-shake(压缩率接近 50%),但对图片、JSON、字体几乎不压缩(保留率接近 80%)。项目实测:源码 2.90MB,打包产物 2126KB,保留率约 73%——但这个比例随包内容不同可以在 50%–80% 大幅波动。
用固定的源码阈值倒推打包体积,既可能「源码才 1.8MB,以为没问题,实际打包已 2.0MB」漏报,也可能「源码 2.1MB 就告警,实际打包才 1.5MB」误报。
正解
立即解堵
分包超限时的快速减重手段:
- 注销已下线页面(从
app.json的 pages 中删除注册,物理文件可暂时保留) - 删除无引用的 wxss 样式和未用图标资源
- 把分包内的大静态 JSON 改为接口动态拉取
长期防护:三层分工
真正可靠的方案是用 miniprogram-ci 做真实编译检测,数字与 DevTools「代码依赖分析」完全一致,可作为硬性阈值的权威来源:
// scripts/check-subpackage-size.js
const ci = require('miniprogram-ci')
const path = require('path')
async function main() {
const project = new ci.Project({
appid: process.env.WX_APPID,
type: 'miniProgram',
projectPath: path.resolve(__dirname, '..'),
privateKeyPath: path.resolve(__dirname, '../ci-private.key'),
ignores: ['node_modules/**', '.git/**']
})
const result = await ci.preview({
project,
desc: 'size-check',
robot: 1,
setting: { es6: true, minify: true, autoPrefixWXSS: true },
qrcodeFormat: 'terminal'
})
const MAX_KB = 2048
const WARN_KB = 1900
let hasError = false
result.subPackageInfo.forEach(pkg => {
const kb = (pkg.size / 1024).toFixed(1)
if (pkg.size > MAX_KB * 1024) {
console.error(`FAIL ${pkg.name}: ${kb}KB > ${MAX_KB}KB`)
hasError = true
} else if (pkg.size > WARN_KB * 1024) {
console.warn(`WARN ${pkg.name}: ${kb}KB (接近上限)`)
} else {
console.log(`OK ${pkg.name}: ${kb}KB`)
}
})
if (hasError) process.exit(1)
}
main().catch(err => { console.error(err); process.exit(1) })但真编译耗时 60–120s,不能塞进每次 push。合理的分层策略:
| 时机 | 方法 | 阻断力 |
|------|------|------|
| 每次 push(pre-push hook) | du -sk 粗估,源码 > 1.8MB 阻断 | 轻量防护 |
| PR check / 每日 cron | miniprogram-ci 真编译,打包 > 1.9MB warn / > 2.0MB fail | 精确强阻断 |
| 发版前 | DevTools「代码依赖分析」手动核查 | 权威确认 |
npm i -D miniprogram-ci,在 CI 的 PR check job 跑真编译- 直接读
result.subPackageInfo[].size,单位 bytes,与 DevTools 数字一致 - 打包体积 > 1.9MB 告警,> 2.0MB fail check,阻止 merge
- pre-push hook 用
du -sk做轻量粗估兜底(阈值按实测压缩率倒推,留充足缓冲) - 发版前开发者手动用 DevTools「代码依赖分析」确认一次
亚稳态危险区分类
| 分包打包体积 | 状态 | 建议动作 | |-----------|------|------| | < 1.5 MB | 健康 | 无 | | 1.5–1.8 MB | 关注 | Review 时提醒体积增量 | | 1.8–1.95 MB | 亚稳态 | CI 告警 + 限制新增 | | > 1.95 MB | 临界 | 阻止 merge,要求先瘦身 | | > 2.0 MB | 爆炸 | 提交全员阻塞 |
一句话外卖
分包体积接近上限时,「每次只加一点点」是最危险的模式——系统进入亚稳态,任何一次普通 commit 都可能成为压垮全员的最后一根稻草;用 miniprogram-ci 真编译做 CI 硬卡,是唯一可靠的防线。