编译构建

我们希望使用目前设计最强大、生态最完善的编译构建工具Webpack来实现小程序的编译构建,让用户得到web开发中先进强大的工程化开发体验。使用过Webpack的同学都知道,通常来说Webpack都是将项目中使用到的一系列碎片化模块打包为一个或几个bundle,而小程序所需要的文件结构是非常离散化的,如何调解这两者的矛盾成为了我们最大的难题。一种非常直观简单的思路在于遍历整个src目录,将其中的每一个.mpx文件都作为一个entry加入到Webpack中进行处理,这样做的问题主要有两个:

  1. src目录中用不到的的.mpx文件也会被编译输出,最终也会被小程序打包进项目包中,无意义地增加了包体积;
  2. 对于node_modules下的.mpx文件,我们不认为遍历node_modules是一个好的选择。

最终我们采用了一种基于依赖分析和动态添加entry的方式来进行实现,用户在Webpack配置中只需要配置一个入口文件app.mpx,loader在解析到json时会解析json中pages域和usingComponents域中声明的路径,通过动态添加entry的方式将这些文件添加到Webpack的构建系统当中(注意这里是添加entry而不是添加依赖,因为只有entry能生成独立的文件,满足小程序的离散化文件结构),并递归执行这个过程,直到整个项目中所有用到的.mpx文件都加入进来,在输出前,我们借助了CommonsChunkPlugin/SplitChunksPlugin的能力将复用的模块抽取到一个外部的bundle中,确保最终生成的包中不包含重复模块。我们提供了一个Webpack插件和一个.mpx文件对应的loader来实现上述操作,用户只需要将其添加到Webpack配置中就可以以打包web项目的方式正常打包小程序,没有任何的前置和后置操作,支持Webpack本身的完整生态。

Mpx编译构建机制流程示意图

Mpx编译构建机制流程示意图

分包处理细节

构建时对于非JS资源,是所有的包串行处理,资源map会精确记录每个包中引用了哪些资源。

在主包的处理过程中,将主包页面中引用的所有非js资源(组件、外部样式、外部模板、wxs,图像媒体等)都记录下来,在处理分包时,对分包内引用的非js资源都进行检查,如果被主包引用过则输出到主包中,否则标记为分包only的资源输出到分包目录下。

由于主包和分包限制相同,一般情况下分包体积远小于主包体积 ,而微信小程序的体积限制策略主要卡主包体积不得超过2M,因此,Mpx设定的是主包体积优先策略,即尽可能减小主包体积,为此,会将分包资源尽可能打到分包去。

组件和静态资源的输出规则如下:

  1. 主包引用的资源输出至主包
  2. 分包引用且主包引用过的资源输出至主包,不在当前分包重复输出
  3. 分包引用且无其他包引用的资源输出至当前分包
  4. 分包引用且其他分包也引用过的资源,重复输出至当前分包
  5. 当用户通过packageName query显式指定了资源的所属包时,输出至指定的包

如此复杂的分包策略也完全是为了尽可能良好地支持处理分包引用npm资源,因此不管在主包还是分包中,使用Mpx框架开发小程序,可以享受最舒适最自然最好用的npm机制。

而小程序原生的分包是不具备这个能力,只能引用自己分包下的资源,同时大部分基于web技术而来的转译型框架应该也都是不会考虑这个部分的。

甚至如果出现这种场景:同一个组件在A分包和B分包中都有使用,但未在主包使用,会将这个组件分别放入A分包和B分包而不是主包。(是否需要这样可自行斟酌,如果不希望出现这种情况,可在主包中引入一次该组件就会被收到主包去,全局就这一份了)

总结一下:

对于组件,若同时被多个分包引用,主包未引用,会有多份分包存在于各分包中。一旦被主包使用,就仅有一份存在于主包中。

对于资源(例如纯JS模块),若被主包使用,会被收集到主包里。若被多个分包使用,会被收集进主包的bundle里。若仅被某分包使用,就会在分包的bundle.js里(视模块大小及引用次数也可能被内联到某个组件JS里)。

进阶用户有更花式的需求可通过packageName query来显示指定输出。