背景

为了减小用户一次性需要下载的代码体积,提高应用加载速度,小程序提出了代码分包的概念:
主包:无论小程序冷启动时正在加载哪个包的页面,都会默认加载主包代码。主包体积不能超过2M。
分包:用户访问分包内的页面,只会下载对应分包内的代码,别的分包不会被下载。

因此对于开发者来说,应该将代码解耦,相关联的代码放在同一个包内来复用。但总会遇到希望跨分包复用代码的场景——例如根据业务 to B 和 to C 有两个分包,但是对订单的优惠信息,希望可以用同一个函数做格式化处理,而不关注是 to B 还是 to C。

初步方案

简单的解决方案是,根据主包一定会默认加载的特性,将格式化的函数写在主包,这样两个分包都可以引用。但随着项目体积膨胀,主包很容易就会达到2M的体积限制,这就涉及到另一个问题,如何在不影响已有业务逻辑的前提下减小包体积:

  • 对于 scss:避免选择器多层嵌套(编译后 css 里会把嵌套的选择器拉平,每一层嵌套都会导致非常长的选择器定义)、利用 @mixin 复用样式代码(@mixin 编译后会变成空的 css 文件,会把这份 mixin 拷贝到对应的分包里去。如果是多个分包使用,主包没有使用的样式,这份 mixin 就不会增加主包体积)
  • 对于 js:利用 Behavior 复用代码
  • 对于 wxml: 利用 wxs 复用代码;利用 复用代码。

跨分包方案

通过上述手段减小主包体积,总归是治标不治本的——需要跨分包复用的代码量会随时间膨胀,而通过「节省」减小主包体积的效果会随时间越来越小,最终总会碰到主包超过体积的问题。

仔细分析需要被复用的代码会发现一个问题:大部分代码在主包没有用到,主包最终是不需要这些代码的。因此提出一种思路:借助打包工具,将这部分代码「剪切」,并「粘贴」进所需的分包中,这样只需要写一份代码,但不会占据主包体积,又可以在多个分包中复用了。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
packageMain
|- reuse
|- a.js
|- b.js
|- c.js

packageA // 需要复用a.js、b.js

packageB // 需要复用b.js、c.js

packageC // 需要复用c.js、a.js

在编码时,依然是维护主包文件夹内的 a.js,但经过打包,a.js 会复制两份,分别进入两个分包——这样就减小了主包体积,而代价是双倍的分包体积。但是总的包体积是 20M,相对于主包只给 2M,已经非常宽裕了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
packageMain // reuse 被移除

packageA
|- reuse
|- a.js // 按需引用,不引入 c.js,减小分包体积
|- b.js

packageB
|- reuse
|- b.js
|- c.js

packageC
|- reuse
|- c.js
|- a.js

遇到的问题

相对路径

在把主包代码改造为跨分包复用代码时,最关键的问题就是各个文件模块的层级结构问题了,以 js 为例,引用模块可能用到很多相对路径(../../xx),但是在跨分包复用的代码中,应该尽量避免使用相对路径:

  1. 相对路径不应该指向分包,这部分代码不应该依赖某个分包里的特定模块,如果有这样的需要,应该把该模块也放到跨分包复用的目录里。
  2. 对于指向主包的相对路径,可以借助打包工具的alias能力去代替相对路径,这样更加直观,可读性更好,也更好维护。
  3. 对于指向跨分包代码的相对路径,则有内部引用的问题

跨分包代码的内部引用

还是用上边的 reuse 文件夹举例,有可能 a.js 内部是依赖 b.js 的,而分包可能只引用(复制)了 a.js,这样会导致分包内的 a 报错——引用了不存在的文件。
对于这类文件内部有依赖关系的问题,目前在工程上没有想到特别优秀的解决方案,还是通过写注释、口头约束等方式来避免这个问题。
也许可以继续借助打包工具,手动维护一份依赖树,在复制a的同时,自动把依赖的b也复制过去。甚至通过打包工具自动生成依赖树。