微信小程序分包进阶:独立分包、预下载与分包异步化的正确打开方式
主包 2M、总包 30M 的限制人人皆知,但分包能玩出的花样远不止"把页面挪出去"。独立分包解决冷启动速度、分包预下载消除切换白屏、分包异步化打破"主包不能引用分包代码"的铁律——这三个特性组合起来,才是分包体系的完全体。这篇逐个拆解。
一、普通分包:只是起点
// app.json
{
"pages": [
"pages/index/index",
"pages/login/login"
],
"subpackages": [
{
"root": "packageOrder",
"pages": [
"pages/list/list",
"pages/detail/detail"
]
},
{
"root": "packageActivity",
"name": "activity",
"pages": ["pages/coupon/coupon"]
}
]
}容易忽略的规则:
- 分包页面可以引用主包的公共资源,反之不行(普通分包模式下)
tabBar页面必须在主包- 分包使用
root做 import 路径前缀:require('../../packageOrder/utils/format.js')从主包引用是不行的(分包异步化出现前) - 一个页面的图片资源如果被多个分包引用,放主包还是复制多份,要用体积权衡
二、独立分包:把启动速度砍一半
2.1 原理
普通分包启动时,无论用户进入哪个页面,主包都会先下载。独立分包的意义:从它进入小程序时,不下载主包,直接跑独立分包。
适用场景非常明确:
- 营销活动页(扫码直达,秒开要求高)
- 分享有礼落地页
- 面向新用户的裂变页面
{
"subpackages": [
{
"root": "packageActivity",
"pages": ["pages/coupon/coupon"],
"independent": true // 关键配置
}
]
}2.2 独立分包的三条军规
军规一:不能依赖主包任何内容。主包的 app.wxss、utils、公共组件全部不可用,独立分包必须是自包含的。
军规二:getApp() 可能拿不到。主包未加载时 app 实例不存在:
// packageActivity/pages/coupon/coupon.js
const app = getApp({ allowDefault: true }) // 允许拿到默认空壳
// 或更稳妥的防御式写法
let app
try {
app = getApp()
} catch (e) {
app = { globalData: {} }
}军规三:App 生命周期不触发。从独立分包冷启动时,app.js 的 onLaunch/onShow 都不会执行。全局初始化逻辑(如统计 SDK 初始化)要在独立分包页面里自己做一份。
2.3 独立分包跳主包
用户从活动页点"进店逛逛"跳主包页面时,主包才开始下载:
wx.navigateTo({
url: '/pages/index/index',
fail() {
// 主包下载中,用户可能看到 loading
wx.showLoading({ title: '加载中' })
}
})跳转前可以预热:
// 独立分包页面 onReady 后预下载主包
wx.preDownloadSubpackage({
packageType: 'main',
complete() { /* 主包就绪 */ }
})三、分包预下载:消灭切换白屏
用户从首页点进订单列表,普通分包要现场下载 → 白屏/loading 一段。preloadRule 让分包提前就位:
{
"preloadRule": {
"pages/index/index": { // 触发页面
"network": "all", // wifi / all
"packages": ["packageOrder"] // 预下载的分包 root 或 name
},
"pages/order/list/list": {
"network": "wifi",
"packages": ["packageActivity"]
}
}
}设计策略:按用户动线配置——首页预下载高频分包(全网络),二级页在 wifi 下预下载低频分包。同一个分包的预下载体积和页面数量成正比,别一口气全预下载,白瞎了流量策略。
进阶玩法是结合数据做个性化预下载:服务端返回用户画像,首页 onLoad 时动态调用 wx.preDownloadSubpackage:
// 首页
async onLoad() {
const profile = await api.getUserProfile()
if (profile.isVip) {
wx.preDownloadSubpackage({
packageType: 'sub', root: 'packageVip',
complete() {}
})
} else {
wx.preDownloadSubpackage({
packageType: 'sub', root: 'packageActivity',
complete() {}
})
}
}四、分包异步化:主包引用分包代码
这是 2022 年后分包体系的最大进化。此前"主包不能 require 分包代码",公共组件库要么塞主包(挤占 2M),要么各分包复制一份。分包异步化允许:
4.1 跨分包 JS 引用
// 主包页面里,引用分包的模块
const { formatPrice } = require.async('../../packageOrder/utils/format.js')
Page({
async onLoad() {
const { formatPrice } = await require.async('../../packageOrder/utils/format.js')
this.setData({ price: formatPrice(9900) })
}
})4.2 跨分包组件引用
// 页面 json
{
"usingComponents": {
"order-card": "../../packageOrder/components/order-card/index"
}
}<!-- WXML:像普通组件一样用,首次渲染时自动异步加载 -->
<order-card wx:if="{{loaded}}" item="{{item}}" />注意搭配 wx:if 或占位组件,处理组件异步加载完成前的渲染状态,避免布局跳动。
4.3 分包异步化的收益场景
| 场景 | 传统方案 | 异步化方案 |
|---|---|---|
| 大型富文本编辑器(500KB) | 塞主包,挤占限额 | 编辑器分包,用到再加载 |
| 多个分包共用图标库 | 每包复制一份 | 图标库分包,异步引用 |
| 低频但主包入口需要的工具 | 塞主包 | 工具分包化 |
五、体积分析实战
上线前用代码依赖分析(开发者工具 → 详情 → 基本信息 → 代码包体积,或 ci.quickCompile 分析)找出大头:
常见的体积黑洞:
- echarts 全量引入(~900KB)→ 按需构建 + 放分包异步化
- moment.js 带全量 locale(~300KB)→ dayjs(7KB)替换
- 图片资源→ CDN 化,包内只留 tabBar 图标等必须本地化的
- 组件库全量引入 → 按需引入 + lazyCodeLoading: requiredComponents
// app.json:按需注入,所有项目都该开
{
"lazyCodeLoading": "requiredComponents"
}六、架构决策速查
主包(≤2M)
├── tabBar 页面(必须主包)
├── 登录/首页骨架
└── 首屏强依赖的公共代码
普通分包(按业务域拆)
├── packageOrder —— 预下载(network: all)
├── packageVip —— 按用户画像动态预下载
└── packageEditor —— 分包异步化,主包按需引用
独立分包(自包含,零主包依赖)
└── packageActivity —— 营销页/裂变页,秒开七、避坑清单
| 坑 | 现象 | 解法 |
|---|---|---|
| 独立分包 getApp() 报错 | 主包未加载 | getApp({allowDefault:true}) 防御 |
| 独立分包样式错乱 | 引用了 app.wxss | 样式自包含,别依赖全局样式 |
| preloadRule 不生效 | 触发页面是 tabBar 页面之外的类型 | 触发页面必须是普通页面 |
| require.async 偶发失败 | 分包未下载 | try-catch + 降级 UI |
| 跨分包组件首帧跳动 | 异步加载无占位 | wx:if + 骨架占位 |
| 真机白屏但工具正常 | 分包路径大小写 | 严格保持目录名大小写一致 |
写在最后
分包体系的三板斧各有分工:普通分包管体积、独立分包管启动、预下载和异步化管体验。做架构时先画用户动线图,再决定哪个页面进哪个包、预下载怎么排布。最后记住一个朴素原则:主包里只放"每个用户每次打开都会用到"的东西,其他一切皆可分包。
评论 (0)