网站已运行 162 · 13小时 · 39 · 53
目录

微信小程序分包与包体积优化:subPackages 配置和预下载

微信小程序主包、普通分包、独立分包、预下载与包体积缩减概念图

微信小程序分包不是把目录随便拆开就结束。真正需要控制的是三件事:哪些页面必须留在主包、哪些业务可以按需下载,以及公共代码和资源放在哪里才不会重复占用包体积。

这篇文章使用原生小程序的 app.json 配置,从普通分包开始,再说明独立分包和 preloadRule。示例同时附带一个本地检查脚本,用于提前发现分包 root 嵌套、tabBar 页面误放分包和预下载名称写错等问题。开始前建议先熟悉微信小程序项目目录结构;如果还没有可运行项目,可以先完成AppID 与微信开发者工具配置

一、主包、普通分包和独立分包怎么分工

代码包适合放什么启动时机主要限制
主包默认启动页、tabBar 页面、多个业务共用的必要代码小程序普通启动时先下载把低频页面和大资源留在主包会拖慢首次下载
普通分包订单、会员、售后等按业务域访问的页面首次进入该分包页面时下载可以引用主包和本分包内容,不能直接依赖其他普通分包
独立分包扫码落地页、活动页等希望不下载主包就能启动的独立入口可独立于主包启动不能假设主包、Appapp.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 可作为预下载时的别名。官方文档同时接受 subPackagessubpackages 两种写法;项目里建议固定一种,避免配置审查时产生无意义差异。

  • 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 会在不限网络类型时预下载,需要评估流量和命中率。

微信开发者工具中 app.json 的 subPackages、independent 和 preloadRule 配置
示例项目在微信开发者工具中的实际配置:订单为普通分包,活动页为独立分包,首页在 Wi-Fi 下预下载订单分包。点击图片可查看大图。

预下载不是越多越好。官方文档规定,同一分包中的页面共享预下载大小限额,开发者工具会在打包时校验;vConsole 中以 preloadSubpackages 开头的日志可用于确认预下载是否发生。应优先预下载用户下一步大概率访问的业务包,而不是在首页把所有分包重新下载一遍。

实测边界:本文使用微信开发者工具 Stable 2.02.2608060 和 iPhone 真机完成连接、构建与页面运行。开启全部日志级别、清除缓存并重启真机调试后,桌面 Console 仍未保留启动阶段的 preloadSubpackages 系统日志。因此,本文不把“桌面 Console 没出现该日志”当作预下载成功或失败的证据,而以配置截图、代码质量检查和真机连接状态共同记录本次验证结果。

六、包体积优化先找“为什么进了主包”

  1. 查看构建后的包组成:以当前微信开发者工具的代码依赖分析、包体积或上传提示为准,不用源码目录大小代替最终结果。
  2. 检查配置范围:未落入任何分包 root 的目录会进入主包,常见问题是把低频组件、图片或数据文件放在根目录公共区。
  3. 检查公共依赖:只有多个包启动时都必须使用的代码才适合放主包;大型库可以按页面真实使用情况拆分或换成更小实现。
  4. 处理静态资源:删除未引用文件,压缩本地图片;需要远程加载的资源还要同时考虑 HTTPS、合法域名、缓存和弱网体验。
  5. 复测冷启动路径:主包变小不等于体验一定更好,还要验证首次进入分包时的下载等待以及预下载命中率。

包体积限制和工具展示可能随平台规则、主体能力或工具版本变化,所以本文不把某个固定总量当成长期常量。发布前应以当前开发者工具的校验结果和微信平台上传反馈为准,并把使用的工具版本记录到项目测试单中。

微信开发者工具代码质量检查通过并显示主包检查结果
微信开发者工具 Stable 2.02.2608060 的代码质量检查结果:未发现代码质量问题,主包大小、JS 文件和组件检查均通过。点击图片可查看大图。

七、用脚本提前检查分包配置

下面的 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 未保留预下载系统日志的情况也已在上文单独说明。

微信开发者工具连接 iPhone 13 进行小程序分包真机调试
iPhone 13 真机调试已连接,连接状态与服务状态正常;测试环境为 iOS 15.1、微信 8.0.75、基础库 3.17.1。点击图片可查看大图。

八、常见错误与定位顺序

现象优先检查处理方向
主包体积没有明显下降低频文件是否仍在分包 root 之外先按构建依赖定位,不要只移动页面文件
tabBar 页面无法正常配置页面是否被放入分包把 tabBar 页面移回最外层 pages
普通分包运行时报模块不存在是否直接引用另一个分包移到本分包、主包,或按官方分包异步化能力重构
独立分包中全局样式或状态丢失是否依赖 app.wxss 或无条件调用 getApp()补齐分包内依赖,让入口真正独立
预下载没有发生页面路径、分包 root/name、网络条件和 vConsole 日志逐项核对 preloadRule,不要盲目改成 all

九、上线前检查清单

  • 默认启动页和所有 tabBar 页面位于主包;
  • 每个分包 root 唯一且互不嵌套;
  • 普通分包没有直接引用其他分包的代码、模板或资源;
  • 独立分包不依赖主包 Appapp.wxss 和公共组件;
  • 预下载只覆盖高概率下一步路径,并在目标网络条件下验证;
  • 开发者工具中的代码依赖分析、包体积与上传校验全部通过;
  • 真机验证冷启动、首次进入分包、返回主包和弱网场景。

分包优化的目标不是“分得越多”,而是让首次启动只携带必要代码,同时让后续业务包在合适的时机下载。先用构建结果定位主包来源,再决定移动目录、拆依赖还是配置预下载,通常比直接新增大量分包更有效。

官方资料:微信开放文档《使用分包》微信开放文档《独立分包》微信开放文档《分包预下载》(访问日期:2026 年 8 月 26 日;独立分包与预下载适用基础库 2.3.0+,开发者工具 1.02.1808300+)

数臻源码猫咪图标
目录
数臻源码猫咪图标

目录

标签云: