十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序分包实战:突破2MB限制与优化启动速度

微信小程序分包实战:突破2MB限制与优化启动速度 做微信小程序开发第一道绕不过去的坎就是包体积。你可能已经遇到过这种情况业务页面还没做几个开发者工具右上角就跳出一行红字“主包大小超过2MB”然后你的代码就传不上去了。这时候微信小程序分包这个方案就不得不认真对待了。微信小程序分包简单来说就是把你的小程序代码按业务场景拆成多个代码包用户打开小程序时只下载主包等真正访问到某个子功能页面时再去下载对应的分包。这个机制解决的是两个非常实际的问题一个是2MB的包体积限制另一个是首屏启动速度。这篇文章不扯官方的文档翻译我就从实际开发经验出发把分包从原理、配置、独立分包到预下载、异步化这些内容完整拆一遍希望对正在做或者准备做微信小程序开发的团队有帮助。先说清楚这篇文章适合谁如果你的小程序已经超过了1.5MB或者页面数量超过30个或者首屏加载白屏时间长又或者你只是想提前知道分包怎么规划才不返工那接下来的内容都会对你有用。1. 微信小程序分包到底解决什么问题1.1 先搞清楚两条硬性红线微信小程序的包体积限制是硬性的绕过不了的。目前官方的规定是整个小程序所有代码包总大小上限是30MB而单个主包或单个分包各自不能超过2MB。这是两条必须同时满足的红线缺一不可。很多人的理解偏差在“30MB”上。认为总包30MB很大放心塞。但注意30MB是所有包的总和而2MB是任何一个包的单包上限。换句话说你哪怕建了15个分包每个1.9MB加起来超过了30MB依然不行。所以做分包不是简单地把代码拆开而是要在“单个不超过2MB”和“总量不超过30MB”之间做一个工程化的分配。这个限制的初衷也很好理解。小程序和传统的App不同用户不需要在应用商店下载安装包而是即点即用。既然要做到“打开就用”微信就必须限制下载体积。2MB在现在这个动辄几十MB的App面前看着很小但它保证了弱网环境下用户也等得起。这个逻辑是所有分包设计的总前提。1.2 分包的加载逻辑把一次性下载改成按需下载不分包的时候小程序所有代码都塞在主包里。用户点击小程序微信需要把整个主包下载完才渲染首屏。分包之后逻辑就变了用户打开时只下载主包当用户从某个入口跳转到分包页面时微信再去下载对应的分包。这套机制就像你在一家餐厅点餐。不分包相当于一进门就被要求把整个菜单上的菜全部先做出来哪怕你只点一个菜而分包则是你先坐着等你点了哪道菜后厨再做哪道菜。从技术实现上讲分包的关键是“按需下载、缓存复用”。分包被下载过一次之后会缓存在本地下次再访问同一个分包页面时基本不需要重新下载。所以用户在一个小程序里停留越久路径走得越深分包带来的收益就越明显。首次加载主包体积变小后续加载分包页面又走缓存整个体验自然就上去了。1.3 什么时候该考虑做分包我见过不少团队在包体积没到2MB的时候就焦虑分包这是没必要的。分包不是越早越好它是带着工程复杂度的。但出现下面几种信号你就得认真规划了主包体积超过1.5MB距离2MB的线越来越近。页面的 JS、图片、第三方库越来越多编译一次要等半天。首屏加载时间明显变长尤其是非WiFi环境下用户流失严重。小程序承载了多个业务模块比如电商里的商品、订单、售后、直播各个模块可以独立迭代。尤其最后一种情况哪怕主包还没超限也应该尽早按业务模块拆分包。因为后面对接的开发会越来越多等到代码全堆在主包里再想拆重构成本会高很多。我自己踩过这个坑项目到后期主包已经1.9MB各个模块的代码纠缠在一起光是拆包就花了整整一个迭代的时间。2. 分包工程落地的正确姿势从目录规划到配置2.1 目录结构怎么规划才不返工分包的第一步不是写配置而是规划目录。微信官方推荐的思路是“root对应一个业务模块”。比如一个电商类小程序合理的结构看起来是这样├── app.js ├── app.json ├── app.wxss ├── pages/ // 主包页面只放核心链路 │ ├── index/ // 首页 │ ├── cart/ // 购物车 │ └── user/ // 个人中心 ├── packageGoods/ // 商品分包 │ ├── pages/ │ │ ├── list/ // 商品列表 │ │ └── detail/ // 商品详情 │ └── utils/ // 分包内公共逻辑 ├── packageOrder/ // 订单分包 │ ├── pages/ │ │ ├── confirm/ // 确认订单 │ │ └── list/ // 订单列表 │ └── utils/ └── packageCommon/ // 公共分包放公共组件和工具 ├── components/ └── utils/注意这里的核心原则主包里只保留用户第一眼必须看到的页面首页、核心Tab页其余所有业务页面都放到对应分包里。别把主包当成所有页面的大本营作为项目入口主包越瘦越好。2.2 app.json 配置逐字段拆解目录规划好之后在 app.json 里注册分包。我看到很多人用官方文档的示例照抄写完了却不理解每个字段的意义。下面是一份完整的配置{ pages: [ pages/index/index, pages/cart/cart, pages/user/user ], subpackages: [ { root: packageGoods, name: goods, pages: [ pages/list/list, pages/detail/detail ] }, { root: packageOrder, name: order, independent: true, pages: [ pages/confirm/confirm, pages/list/list ] } ], preloadRule: { pages/index/index: { network: all, packages: [goods] } } }这里几个容易被忽略的细节subpackages 和 subPackages 两种写法微信都认但我建议统一用一种免得团队里有人改错。root 是分包的根目录这个目录下的所有文件都会被打进这个分包。name 是分包的别名主要配合预下载的 packages 字段使用。pages 数组里的路径不需要写 root 前缀配置时系统会自动拼上。有个很重要的点pages 数组里的主包页面必须包含小程序的启动页和tabBar页面不能全部塞进分包里。另外分包里不能再嵌套分包这是死规矩搞错的话编译直接报错。2.3 页面跳转与路径引用几个容易踩的规则分包配置完之后页面之间的跳转路径就变了。分包内的页面完整路径是 “root pages路径”比如 packageGoods 分包的 list 页面完整路径是/packageGoods/pages/list/list。这个路径在 wx.navigateTo、wx.redirectTo、组件跳转里都要写完整。很多新手在这里犯迷糊本来在主包里写/pages/list/list写习惯了到了分包里忘了加 root 前缀结果真机上跳到空白页。还有一点要注意主包页面可以跳到分包页面分包页面也可以跳回主包页面路径写对就行。唯一跳不过去的是“分包A跳到分包B”的常规跳转这种跨分包跳转虽然微信允许但会触发分包B的重复加载逻辑实际体验并不好。资源引用同样有规则。分包的 WXML 里可以引用主包的图片、公共样式普通分包可以 require 主包的 JS 文件。反过来主包不能直接 require 分包里的文件——这需要通过后面会讲到的分包异步化来解决。搞清楚这个引用方向能帮你省很多排查报错的时间。2.4 tabBar 页面不能进分包这是死规矩这个坑我见得太多了。tabBar 页面是微信底部的“首页、分类、购物车、我的”这些必须常驻的页面它们承担着小程序的骨架作用因此必须全部放在主包的 pages 数组里不能放进任何分包。如果非要把 tabBar 页面配置到分包里开发者工具会直接报错告诉你分包不能包含 tabBar 页面。于是这里就出现了一个现实问题tabBar 页面太多每个 tab 页面都很重主包体积还是会居高不下。怎么解决把 tab 页面本身做轻。tab 页面只负责入口框架具体的子页面塞进分包。比如一个商城小程序“分类” tab 页面做成一个承载框架实际分类下的商品列表逻辑放在 packageGoods 分包里通过一个小程序页面跳转过去。这样 tab 页面自身很轻主包压力就小了。3. 独立分包与分包预下载性能优化的两个关键杠杆3.1 独立分包让某些页面彻底摆脱主包依赖基础分包做到位之后下一步要接触的是独立分包。独立分包的核心特征是它不依赖主包访问独立分包页面时微信只会下载独立分包不会下载主包。这个机制对于某些特殊场景非常有价值。我举一个实际案例。很多小程序都有“分享领红包”这种活动页用户从聊天会话里点开分享卡片直接落到活动页面。如果这个页面在普通分包里微信会先下载主包再下载分包用户在一个短视频平台上等了半天才看到红包页面分享转化率基本就报废了。把活动页放进独立分包之后用户点开分享卡片微信直接下载这个独立分包页面秒开。配置独立分包只需要在分包上多加一个字段{ root: packageActivity, name: activity, independent: true, pages: [ pages/hongbao/hongbao ] }需要注意独立分包的独立性是双向的它不能依赖主包的任何 JS、模板、样式、图片资源主包也不能 require 它里面的文件。所以独立分包里的资源要尽量自给自足。如果页面里用到了公共组件也要复制一份到独立分包目录下。另外独立分包页面之间可以互相跳转也可以跳转到主包页面但跳转时会触发主包下载。开发时我用独立分包来做营销页、支付结果页、预约成功页这类“用完即走”的页面效果都很好。3.2 分包预下载把等待时间提前花掉分包解决了主包体积问题但也带来一个新的体验问题用户首次进入某个分包页面时微信需要现下载分包这个等待过程在弱网环境下可能长达好几秒。分包预下载机制就是为了解决这个问题设计的。预下载的配置方式和字段含义如下{ preloadRule: { pages/index/index: { network: all, packages: [goods, order] } } }这段配置表达的逻辑是当用户进入首页时在当前网络条件满足 network 字段设置的条件下预先下载 goods 和 order 这两个分包。network 字段有两个值all 表示不管WiFi还是流量都预下载wifi 表示只在WiFi环境下预下载默认是 wifi。packages 可以填分包的 root 或 name也可以填字符串APP来表示主包。从实际体验来看预下载最适合用在“用户大概率会去”的分包上。比如用户访问首页大概率会进入商品详情那就预下载商品分包用户把商品加入购物车大概率会去结算那就预下载订单分包。这种“路径预判”做得好分包页面的打开速度能接近主包页面。3.3 预下载策略怎么定才合理预下载不是越多越好。每个分包都是几百KB起步一口气把全部分包都预下载了那和当初的大主包有什么区别所以这里要做取舍。我的实践原则是只预下载“下一个动作大概率会访问到的分包”不要预下载所有分包。举个例子电商小程序的首页预下载商品列表分包是合理的因为首页用户必然会点进商品。但首页不要去预下载用户中心的订单管理分包因为从首页直接跳订单管理的概率很低预下载就是浪费流量和带宽。另外network 字段的选择要谨慎。对流量比较敏感的用户如果你在非WiFi环境下预下载了几个大分包用户会感觉到明显的流量消耗这在小程序体验里是个不小的减分项。我的建议是除非业务特别需要秒开否则 network 保持默认的 wifi 就好。独立分包和预下载这两个机制经常搭配使用。独立分包追求的是“首次打开快”所以一般不做预下载普通分包追求的是“后续打开快”所以适合做预下载。把握住这个区别你的分包策略就很清晰了。4. 分包异步化处理跨分包依赖的进阶玩法4.1 分包之间的引用边界在哪里先理清一个基础分包和分包之间默认是不能互相引用 JS 文件的。分包可以 require 主包里的模块但两个不同的分包之间A 分包想要 require B 分包里的工具函数编译时会直接报错。这个限制让很多业务代码在分包化时遇到麻烦尤其是公共逻辑的放置问题。常见的做法很多比如把公共逻辑全部放到主包的 utils 目录里让各个分包去 require。但这种做法在主包体积紧张的时候就变得不太划算毕竟公共代码也是会膨胀的。另一个做法是在每个分包里各放一份公共代码副本简单粗暴但会增加重复代码而且维护起来非常痛苦。每次改一个公共函数需要记得去每个分包里同步改一遍。这两种方案都需要权衡直到官方推出了分包异步化。4.2 require.async 与 import() 的用法分包异步化允许分包之间、主包与分包之间进行异步的代码引用。也就是说你可以在主包里异步加载某个小程序的 JS 模块也可以在分包A里异步加载分包B的模块。这个机制的核心 API 是 require.async。实际使用方式如下// 在主包页面中异步加载 goods 分包里的某个模块 require.async(../packageGoods/utils/price.js).then((mod) { const formatPrice mod.formatPrice; console.log(formatPrice(99.5)); }); // 也可以 async/await Page({ async onLoad() { const mod await require.async(../packageOrder/utils/order.js); mod.initOrder(); } });除了 require.async微信也支持用标准的 import() 语法做动态加载本质上是同一套能力。底层机制是当代码执行到 require.async 时如果目标模块所在的分包还没下载微信会自动先下载那个分包再执行模块代码。整个流程对开发者是透明的你只需要关心路径和异步时序。这个能力的好处是真正跨分包共享的代码不用塞进主包也不用复制多份。比如商品价格格式化、时间格式化这类高频工具函数原本因为主包体积紧张而纠结放哪现在可以单独打一个分包需要时再异步加载。4.3 分包异步化的使用边界与兼容性分包异步化虽然好用但不要轻易把所有跨分包依赖都改成它。有几个明确的使用边界第一启动路径上的核心逻辑不要用异步化。异步加载天然有延迟如果首页一打开就依赖某个分包模块首屏又会变慢这就背离了分包提速的初衷。第二同步位置的代码不能直接用。比如 Page 构造器里 onLoad 之外的同步逻辑里用 require.async可能出现代码执行顺序和预期不一致的情况。第三注意基础库版本。require.async 最低需要基础库 2.1.0独立分包最低需要 1.7.3分包预下载最低需要 2.3.0。在开发者工具里确认一下项目使用的最低基础库版本如果目标用户里还有大量旧版本微信客户端就要做降级方案。我自己的习惯是把异步化当成一种“例外手段”而不是默认手段。能用普通分包配置解决的不搞异步化只有确实碰到跨分包共享代码、且不适合放进主包的情况才用 require.async。5. 一个真实案例分包改造前后的数据对比5.1 项目背景与分包方案说一个我之前参与的商城类小程序项目。这个项目起初没有做分包规划所有的业务页面全堆在主包里到后期主包体积到了 1.8MB 左右越来越逼近 2MB 的红线。更让人头疼的是首屏表现在 4G 网络下首屏耗时平均 3.2 秒用户流失率很高。团队决定做一次分包改造目标是把主包压到 1.2MB 以内首屏耗时降低 30% 以上。我们的改造方案是这样拆的主包只保留首页、购物车、个人中心三个 tab 页面和启动必须的基础配置商品相关页面放进 packageGoods 分包订单和售后相关页面放进 packageOrder 分包营销活动页面放进 packageActivity 独立分包。同时在 app.json 里配置了预下载规则首页预下载 packageGoods商品详情页预下载 packageOrder。5.2 改造前后对比改造完成之后我记录了一组数据。主包从 1.8MB 降到 1.1MB首屏耗时从 3.2 秒降到 1.8 秒降幅接近 44%。商品详情页的打开耗时从原来的主包加载完成后直接渲染变成了需先下载分包再渲染但由于预下载生效这个页面在用户从首页进入时分包基本已经就绪实际打开耗时只多了 0.3 秒左右用户基本感知不到。还有一个比较意外的收益是开发体验。分包之后每个业务团队只需要关注自己的分包目录编译速度明显提升不同分包之间代码冲突的概率也降低了不少。过去每个人都在改主包时容易互相覆盖分包之后这种情况基本绝迹。5.3 复盘谁收益最大谁收益不明显这个项目改造下来收益最大的是占用户访问量 60% 以上的商品浏览链路因为首页和商品页的加载速度都有提升。收益不明显的是低频营销页因为访问量本来就少体感不出来。但我依然把它们放进了独立分包理由是这些页面的入口大多来自外部分享卡片独立分包最契合这种“单页直达”的场景相比普通分包它省掉了主包下载这一层等待。这次改造让我更深刻地认识到分包不是一个单纯的“体积瘦身”工具它同时是一个性能优化工具也是一个工程组织工具。做得好收益是立体多维度的但前提是一定要基于用户的访问路径来做拆包决策而不是拍脑袋按团队分工硬拆。6. 常见问题与排查技巧实录6.1 编译报错分包大小超过限制这个报错出现的时候开发者工具会提示你具体是哪个包超过了限制。有人看到报错会想去压缩图片、压缩JS这些能缓解一时但不解决根本问题。正确思路是继续拆如果主包超限把可以晚加载的页面继续下沉到分包如果某个分包超限说明这个业务模块本身太臃肿要进一步拆分。我遇到过一种比较隐蔽的情况分包本身不大但工具报出某个分包超过2MB。后来发现是分包里塞了一张很大的背景图或者引入了体积巨大的第三方组件库。图片资源这个坑很常见尤其是设计给的背景图动不动就是1MB的 PNG。我的建议是图片能压缩就压缩大图尽量用网络图片不要打进代码包里静态资源同样受包大小限制约束。6.2 真机页面白屏或找不到分包上线后最常见的故障是页面白屏。出现这个问题的原因大多是路径写错了。比如在分包页面里跳转到同一个分包下的另一个页面有些人会习惯性地省略 root 前缀结果是跳转后一个空白页控制台报找不到页面。排查的方法很简单先在开发者工具里看页面是否正常如果工具正常但真机白屏多半是路径或缓存问题。真机上可以试一下清除小程序缓存重新进入如果恢复正常说明是旧代码缓存导致的。另外如果你用了一些低版本的基础库而代码里又用了未兼容的 API也会出现白屏。在工具右上角的“详情 - 本地设置”里可以切换调试基础库版本逐版本排查兼容性。6.3 require.async 报错与处理使用 require.async 时最常见的报错是路径找不到以及模块内部引用了其他模块导致加载失败。require.async 的路径是相对于当前 JS 文件的不是相对于小程序根目录这一点特别容易踩坑。如果模块里还 require 了同分包的其他文件异步加载时会继续解析相对路径路径一错就整个失败。我的建议是在异步加载的模块里尽量保持依赖简单不要嵌套一层又一层否则排查起来非常麻烦。报错时不要只盯着控制台最后一行把完整的堆栈展开找到第一个报错的 resolve 路径问题基本就定位了。6.4 真机调试请求无法到达后端这个问题虽然不是分包直接引发的但在分包改造后出现频率会变高因为分包会让人改动 app.json 和页面配置。遇到真机上请求不到后端先确认手机和开发机不在同一个不可访问的局域网场景下。开发调试时常用的做法是在开发者工具里勾选“不校验合法域名”真机调试时打开调试模式才能访问非 HTTPS 接口。如果这些都没问题再看一下是不是分包代码里用了旧的后端地址或者是静态资源走了本地路径导致环境不对。真机调试请求的问题经验法则就一句话先看基础环境再看域名配置最后看代码逻辑不要一上来就怀疑分包机制。分组之后链路变长反而容易让人兜圈子。6.5 分包相关报错速查表把我在实际中遇到的高频报错整理成一个速查表方便你直接对着排查报错/表现常见原因处理建议主包大小超过2MB主包页面太多或资源太大继续下沉页面到分包图片走云端分包大小超过2MB分包内部资源过多拆分子包或压缩图片、减少第三方库找不到页面跳转路径缺少 root 前缀检查完整路径/root/pages/xxtabBar 页面不能放在分包配置了 tabBar 页在分包里把 tabBar 页面迁回主包 pagesrequire.async 加载失败相对路径错误或模块嵌套依赖展开堆栈修正相对路径简化依赖独立分包访问白屏独立分包使用了主包资源把依赖资源复制进独立分包目录分包下载失败网络异常或包名配置错误检查 name/root 是否匹配 preloadRule这个表格基本覆盖了分包开发从编译到上线过程中常见的坑。遇到问题时可以先对号入座能省不少排查时间。做微信小程序分包这件事回顾下来我的体会是它真正考验的不是你对 API 的熟悉程度而是你对业务链路的理解深度。拆分之前你要清楚地知道用户会从哪里来到哪里去、哪些模块可以延迟加载、哪些页面需要秒开。拆包方案是一张静态的目录结构图但它的背后其实是动态的用户行为路径。我在改造项目的过程中最花时间的反而不是写配置而是分析业务入口和访问路径反复确认每个模块的归属。最后再分享一个小技巧每次调整完分包配置都建议在“真机预览”里完整走一遍主链路别只看开发者工具的模拟效果。模拟器和真机在分包下载时机、缓存策略上是有差别的只有真机上看到的加载结果才最接近用户真实体验。只要把分包当成一个和业务深度绑定的工程来做而不是当成一个敷衍完事的配置任务它是能实实在在帮你提升用户体验的。
返回列表