微信小程序分包与包体积优化:subPackages 配置和预下载
微信小程序分包不是把目录随便拆开就结束。真正需要控制的是三件事:哪些页面必须留在主包、哪些业务可以按需下载,以及公共代码和资源放在哪里才不会重复占用包体积。
这篇文章使用原生小程序的 app.json 配置,从普通分包开始,再说明独立分包和 preloadRule。示例同时附带一个本地检查脚本,用于提前发现分包 root 嵌套、tabBar 页面误放分包和预下载名称写错等问题。开始前建议先熟悉微信小程序项目目录结构;如果还没有可运行项目,可以先完成AppID 与微信开发者工具配置。
一、主包、普通分包和独立分包怎么分工
| 代码包 | 适合放什么 | 启动时机 | 主要限制 |
|---|---|---|---|
| 主包 | 默认启动页、tabBar 页面、多个业务共用的必要代码 | 小程序普通启动时先下载 | 把低频页面和大资源留在主包会拖慢首次下载 |
| 普通分包 | 订单、会员、售后等按业务域访问的页面 | 首次进入该分包页面时下载 | 可以引用主包和本分包内容,不能直接依赖其他普通分包 |
| 独立分包 | 扫码落地页、活动页等希望不下载主包就能启动的独立入口 | 可独立于主包启动 | 不能假设主包、App 或 app.wxss 已存在 |
普通分包解决“按业务加载”,独立分包解决“从特定入口更快启动”。不要因为独立分包听起来更快就全部设置为 independent:它要求页面真正摆脱主包依赖,公共状态、全局样式和组件引用都需要重新审计。
二、先按业务边界整理目录
├── app.js
├── app.json
├── app.wxss
├── pages
│ ├── home
│ └── profile
└── packages
├── order
│ └── pages
│ ├── list
│ └── detail
└── campaign
└── pages
└── landing
首页和“我的”属于高频主流程,并且是 tabBar 页面,因此保留在主包。订单列表与详情放进同一个普通分包;活动落地页需要支持从分享或二维码直接进入,才考虑独立分包。这样的目录边界比按文件类型分包更容易维护。
三、在 app.json 配置 subPackages
{
"pages": [
"pages/home/index",
"pages/profile/index"
],
"subPackages": [
{
"root": "packages/order",
"name": "order",
"pages": [
"pages/list/index",
"pages/detail/index"
]
},
{
"root": "packages/campaign",
"name": "campaign",
"pages": [
"pages/landing/index"
],
"independent": true
}
],
"tabBar": {
"list": [
{
"pagePath": "pages/home/index",
"text": "首页"
},
{
"pagePath": "pages/profile/index",
"text": "我的"
}
]
}
}
root 是分包根目录,pages 写相对于该 root 的页面路径,name 可作为预下载时的别名。官方文档同时接受 subPackages 和 subpackages 两种写法;项目里建议固定一种,避免配置审查时产生无意义差异。
subPackages配置路径之外的目录会进入主包;- tabBar 页面必须属于最外层
pages,不能放进分包; - 一个分包 root 不能位于另一个分包 root 之下;
- 普通分包可以引用主包和自身内容,不能直接跨到其他普通分包。
四、独立分包为什么容易踩坑
独立分包从自己的页面启动时,不需要先下载主包。这也意味着主包中的 App 可能尚未注册,直接调用 getApp() 可能得到 undefined;主包的 app.wxss 也不会自动成为独立分包的样式基础。
// 独立分包中,不要无条件读取主包全局状态
const app = getApp({ allowDefault: true })
Page({
onLoad(options) {
const campaignId = options?.campaignId || ''
this.setData({ campaignId })
}
})
allowDefault 可以在 App 尚未定义时提供默认对象,但它不能替你解决依赖设计。更稳妥的做法是让独立分包页面自行获得启动参数、请求必要数据,并把必需组件与样式保留在分包内。只有页面确实能独立运行时才开启 independent。
五、用 preloadRule 预下载下一步可能访问的分包
{
"preloadRule": {
"pages/home/index": {
"network": "wifi",
"packages": ["order"]
}
}
}
preloadRule 的 key 是进入后触发预下载的页面路径,packages 可以写分包的 root 或 name。network 默认是 wifi;设置为 all 会在不限网络类型时预下载,需要评估流量和命中率。

预下载不是越多越好。官方文档规定,同一分包中的页面共享预下载大小限额,开发者工具会在打包时校验;vConsole 中以 preloadSubpackages 开头的日志可用于确认预下载是否发生。应优先预下载用户下一步大概率访问的业务包,而不是在首页把所有分包重新下载一遍。
实测边界:本文使用微信开发者工具 Stable 2.02.2608060 和 iPhone 真机完成连接、构建与页面运行。开启全部日志级别、清除缓存并重启真机调试后,桌面 Console 仍未保留启动阶段的 preloadSubpackages 系统日志。因此,本文不把“桌面 Console 没出现该日志”当作预下载成功或失败的证据,而以配置截图、代码质量检查和真机连接状态共同记录本次验证结果。
六、包体积优化先找“为什么进了主包”
- 查看构建后的包组成:以当前微信开发者工具的代码依赖分析、包体积或上传提示为准,不用源码目录大小代替最终结果。
- 检查配置范围:未落入任何分包 root 的目录会进入主包,常见问题是把低频组件、图片或数据文件放在根目录公共区。
- 检查公共依赖:只有多个包启动时都必须使用的代码才适合放主包;大型库可以按页面真实使用情况拆分或换成更小实现。
- 处理静态资源:删除未引用文件,压缩本地图片;需要远程加载的资源还要同时考虑 HTTPS、合法域名、缓存和弱网体验。
- 复测冷启动路径:主包变小不等于体验一定更好,还要验证首次进入分包时的下载等待以及预下载命中率。
包体积限制和工具展示可能随平台规则、主体能力或工具版本变化,所以本文不把某个固定总量当成长期常量。发布前应以当前开发者工具的校验结果和微信平台上传反馈为准,并把使用的工具版本记录到项目测试单中。

七、用脚本提前检查分包配置
下面的 Node.js 脚本不替代微信开发者工具打包,它只做适合放进 CI 的静态门禁:解析 app.json,确认 root 不重复或嵌套、tabBar 页面仍在主包,以及预下载规则引用了真实分包。
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const config = JSON.parse(await readFile('./app.json', 'utf8'))
const subPackages = config.subPackages ?? config.subpackages ?? []
const roots = subPackages.map(({ root }) => root.replace(/\/$/, ''))
assert.equal(new Set(roots).size, roots.length, '分包 root 不能重复')
for (const root of roots) {
assert.ok(
roots.every((other) => other === root || !other.startsWith(root + '/')),
'分包 root 不能互相嵌套:' + root
)
}
for (const item of config.tabBar?.list ?? []) {
assert.ok(
config.pages.includes(item.pagePath),
'tabBar 页面必须位于主包:' + item.pagePath
)
}
console.log('PASS: ' + config.pages.length + ' 个主包页面,' + subPackages.length + ' 个分包')
本站测试稿使用 Node.js 运行完整检查脚本,结果为:PASS: 2 个主包页面,2 个分包,1 条预下载规则。随后在微信开发者工具 Stable 2.02.2608060 中重新构建,代码质量页面显示“未发现代码质量问题”,主包大小、JS 文件和组件检查均通过;iPhone 真机调试的连接状态和服务状态也正常。静态检查与工具检查都不能替代真实业务的冷启动和弱网测试,桌面 Console 未保留预下载系统日志的情况也已在上文单独说明。

八、常见错误与定位顺序
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 主包体积没有明显下降 | 低频文件是否仍在分包 root 之外 | 先按构建依赖定位,不要只移动页面文件 |
| tabBar 页面无法正常配置 | 页面是否被放入分包 | 把 tabBar 页面移回最外层 pages |
| 普通分包运行时报模块不存在 | 是否直接引用另一个分包 | 移到本分包、主包,或按官方分包异步化能力重构 |
| 独立分包中全局样式或状态丢失 | 是否依赖 app.wxss 或无条件调用 getApp() | 补齐分包内依赖,让入口真正独立 |
| 预下载没有发生 | 页面路径、分包 root/name、网络条件和 vConsole 日志 | 逐项核对 preloadRule,不要盲目改成 all |
九、上线前检查清单
- 默认启动页和所有 tabBar 页面位于主包;
- 每个分包 root 唯一且互不嵌套;
- 普通分包没有直接引用其他分包的代码、模板或资源;
- 独立分包不依赖主包
App、app.wxss和公共组件; - 预下载只覆盖高概率下一步路径,并在目标网络条件下验证;
- 开发者工具中的代码依赖分析、包体积与上传校验全部通过;
- 真机验证冷启动、首次进入分包、返回主包和弱网场景。
分包优化的目标不是“分得越多”,而是让首次启动只携带必要代码,同时让后续业务包在合适的时机下载。先用构建结果定位主包来源,再决定移动目录、拆依赖还是配置预下载,通常比直接新增大量分包更有效。
官方资料:微信开放文档《使用分包》、微信开放文档《独立分包》、微信开放文档《分包预下载》(访问日期:2026 年 8 月 26 日;独立分包与预下载适用基础库 2.3.0+,开发者工具 1.02.1808300+)




