某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个功能的分包,其中每个分包小程序必定含有一个主包,所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本,而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,如果用户需要打开分包内某个页面,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 8M
- 单个分包/主包大小不能超过 2M
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
使用方法
假设支持分包的小程序目录结构如下:
├── app.js ├── app.json ├── app.wxss ├── packageA │ └── pages │ ├── cat │ └── dog ├── packageB │ └── pages │ ├── apple │ └── banana ├── pages │ ├── index │ └── logs └── utils
开发者通过在 app.json subPackages
字段声明项目分包结构:
{ "pages":[ "pages/index", "pages/logs" ], "subPackages": [ { "root": "packageA", "pages": [ "pages/cat", "pages/dog" ] }, { "root": "packageB", "pages": [ "pages/apple", "pages/banana" ] } ] }
打包原则
- 声明
subPackages
后,将按subPackages
配置路径进行打包,subPackages
配置路径外的目录将被打包到 app(主包) 中 - app(主包)也可以有自己的 pages(即最外层的 pages 字段)
subPackage
的根目录不能是另外一个subPackage
内的子目录- 首页的 TAB 页面必须在 app(主包)内
引用原则
packageA
无法 requirepackageB
JS 文件,但可以 requireapp
、自己 package 内的 JS 文件packageA
无法 importpackageB
的 template,但可以 requireapp
、自己 package 内的 templatepackageA
无法使用packageB
的资源,但可以使用 app、自己 package 内的资源
低版本兼容
由微信后台编译来处理旧版本客户端的兼容,后台会编译两份代码包,一份是分包后代码,另外一份是整包的兼容代码。 新客户端用分包,老客户端还是用的整包,完整包会把各个 subpackage 里面的路径放到 pages 中。
下面以转转为例讲一下:
“离线包”机制
微信小程序采用的是类似离线包加载方案,以转转小程序为例,当用户第一次打开时会先下载好所有代码,然后再加载页面;当用户再次进入转转小程序时,会直接使用已下载的代码,省去了代码下载的过程,打开速度更快。
看似很美好的设计,但有两个问题:
- 第一次打开转转小程序时白屏时间很长,因为要下载接近2.5M的代码量,也就是说你的代码越多,白屏时间越长,而转转APP采用的网页离线机制体验更佳:在用户打开APP时就下载/更新离线包,这样在用户进入对应的网页时,代码已经下载好了,没有漫长的白屏过程。
- 代码有部分更新时,没办法进行增量更新,导致每次发版后,用户都需要重新下载全部代码
问题看似不大,但对转转有很大影响,例如进行微信广告投放时,用户从点击广告到加载第一个页面之间的流失率竟能到达40%,这显然是FE无法接受的性能,而小程序分包加载机制能够在一定程度上解决上述问题。
分包加载
小程序的分包加载机制实际上是离线包和M页的一种结合机制,即你可以把代码划分成主包+N个分包,官方定义:
在小程序启动时,默认会下载主包并启动主包内页面,如果用户需要打开分包内某个页面,客户端会把对应分包下载下来,下载完成后再进行展示。
总结如下:
- 打开小程序,默认先加载主包
- 进入分包页面时,再加载对应分包
这样的好处是进入主包页面时,需要下载的代码量小了很多,白屏时间更短,体验更佳。
特性
- 1.7.3 及以上基础库开始支持,不支持的版本默认使用整包的方式
- 整个小程序所有分包大小不超过 8M,单个分包/主包大小不能超过 2M
- 分包数量目前没有限制,也就是说你可以放N个分包,甚至每个页面一个分包
- 入口页面/TAB页面必须在主包里
关于主包
- 第一次进入小程序,默认下载主包代码
- 分包以外的所有代码,都会被打入主包
- 分包内代码可以引用主包内代码
关于分包
- 因为存在资源依赖关系,微信的机制是先下载主包,后下载分包
- 分包目录不能在主包目录下面
- 分包可以引用自己包内、主包内的资源,不能引用其他分包内的资源
坑
- 小程序的打包机制仅仅是根据文件目录打包,分包内require/import的任何文件,只要不在同一个目录下面,都不会被打进分包,也就是说,类库及一些公共文件,只能放在主包里面,如果主包分包划分不好的话,主包的大小也很难降下来
- 安卓系统进入分包页面时,会出现一个丑陋的系统级的loading层,这一定程度上影响了安卓的体验
转转的分包加载
转转小程序在使用分包之前,压缩后的代码量大概是2.45M,也就是说,每个新用户第一次都需要下载的2.45M代码才能进入页面,使用分包机制后,主包大小降为1M左右,也就是说,如果是进入主包页面,下载时间大约降低了60%
文件结构:
├── libs ├── components ├── pages 主包根目录 ├────index 首页 ├────post 发布页 ├────... ├── subPages 分包根目录 ├────trade 交易分包 ├────mine 我的页面分包 ├────...
我们根据用户访问的轨迹,分成了20个左右的分包。 例如trade包,里面包含详情页、下单页、支付页、支付成功页等,这条线的页面,用户可能不需要一进入小程序就使用,但一旦使用可能是使用整个链条,因此可以作为一个分包。
历史入口兼容
一个页面放入分包之后,路径会发生变化,例如详情页由/pages/detail变为/subPages/trade/detail,意味着如果用户访问了以前的page则得不到正确的页面响应(例如:分享出去的小程序卡片、二维码、公众号推送消息等),这些静态不可改变的历史入口怎么办?我们目前采用如下方案:
原来主包内的每个页面都保留,但代码只保留跳转逻辑,用户进来后立即跳到对应的分包页面,用户几乎是无感知的
这样也会产生一点小问题:这些跳转页面也占用一定的空间,接下来我们会优化成在onLaunch、页面跳转时进行判断,直接跳入正确的分包页面。