引言

随着vue3的正式发布,vite这个尤大投入了大量精力的库的关注度也在上升,尽管还没有发布release版本,但是其github上已经有12k的star了,vite 是一个 web 开发构建工具,在开发过程中通过本地 ES Module 导入为代码提供服务,并将其与 Rollup 捆绑在一起用于生产。vite本身是一个为了加快开发的构建速度的开发时构建工具,与vite类似的还有早于vite发布的snowpack,都是基于现代ESM构建的构建工具。


概述

随着我们的项目越来越大,我们基本都会遇到这样的问题,项目开发5分钟,构建一小时,随着项目体量的增大,模块的增多,基于 webpack 构建的项目开发速度已经非常慢了,即使是hot reload 我们都需要等待近一分钟甚至更久。snowpack和vite的出现就是为了解决这个问题。


ESM

了解snowpack和vite之前,我们要先了解下ESM。


ESM是ES6中提出的官方标准化模块系统,不同于之前的commonjs,AMD,CMD等等,ESM提供了更原生以及更动态的模块加载方案,最重要的就是它是浏览器原生支持的,也就是说我们可以直接在浏览器中去执行import,动态引入我们需要的模块,而不是把所有模块打包在一起。


这也就是snowpack和vite能实现的基础。


ESM的执行可以分成三步:

1.构建

2.实例化

3.运行


第一步构建,对于每个模块,在构建阶段会做三个处理:

最终会将所有模块记录形成一个模块依赖关系树,构建阶段完成后,我们从最开始只有一个入口文件,到现在得到了一堆模块记录。当然模块记录是没有办法被我们直接使用的,所以我们需要将这些模块记录实例化,这也就是下一步,实例化。


模块实例化,我们需要将模块记录转换为一个模块实例,模块实例结合了代码和状态。状态存储在内存中,所以实例化的过程就是把所有值写入内存的过程。


首先,JS 引擎会创建一个模块环境记录(Module Environment Record)。它管理着模块记录的所有变量。然后,引擎会找出多有导出在内存中的地址。模块环境记录会跟踪每个导出对应于哪个内存地址。


这些内存地址此时还没有值,只有等到运行后它们才会被填充上实际值。有一点要注意,所有导出的函数声明都在这个阶段初始化,这会使得后面的运行阶段变得更加简单。


最终,引擎会把模块下的所有依赖导出链接到当前模块。然后回到上一层把模块的导入链接起来。


这个过程跟 CJS 是不同的。在 CJS 中,整个导出对象在导出时都是值拷贝。即,所有的导出值都是拷贝值,而不是引用。所以,如果导出模块内导出的值改变了,导入模块中导入的值也不会改变。


相反,ESM 则使用称为实时绑定(Live Binding)的方式。导出和导入的模块都指向相同的内存地址(即值引用)。所以,当导出模块内导出的值改变后,导入模块中的值也实时改变了。


实例化阶段完成后,我们得到了所有模块实例,以及已完成链接的导入、导出值。现在我们可以开始运行代码并且往内存空间内填充值了。


最后一步是往已申请好的内存空间中填入真实值。JS 引擎通过运行顶层代码(函数外的代码)来完成填充。


除了填充值以外,运行代码也会引发一些副作用(Side Effect)。例如,一个模块可能会向服务器发起请求。因为这些潜在副作用的存在,所以模块代码只能运行一次


由于实例化阶段中发生的链接可以多次进行,并且每次的结果都一样。但是,如果运行阶段进行多次的话,则可能会每次都得到不一样的结果。


这正是为什么会使用模块映射的原因之一。模块映射会以 URL 为索引来缓存模块,以确保每个模块只有一个模块记录。这保证了每个模块只会运行一次。


关于ESM的更详细的内容,可以参见:https://segmentfault.com/a/1190000014318751


snowpack

snowpack是基于ESM的特性开发的开发时构建工具,其核心特征为


snowpack提供了类似cra的cli工具来快速创建snowpack应用, Create Snowpack App (CSA),我们可以通过csa快速创建应用

npx create-snowpack-app new-dir --template [SELECT FROM BELOW] [--use-yarn]

当然我们也可以在已有的项目中添加snowpack.config.json配置文件来使用snowpack,这方面与webpack等构建工具基本一致。


snowpack内置支持以下文件类型的打包构建,不需要额外引入,

当然对于vue,scss等特殊文件,需要我们特别定制一下,比如引入对应的插件(@snowpack/plugin-vue等)


snowpack的打包策略与webpack不同,使用非捆绑式开发,下面这张图展示了webpack和snowpack在build上的区别,可以看到webpack会使用bundle将不同模块最终整合起来,这就意味着,每个模块的变动最终都需要重新记性整合,而snowpack的每个文件都是单独构建的,并且无限缓存。你的开发环境不会建立一个文件超过一次,你的浏览器也不会下载一个文件两次(直到它改变)。当更新的时候,只需要重新构建改变的那个文件,并将其发送到浏览器即可。


image.png

同样,对于我们的npm依赖,snowpack会将所有我们的npm依赖都打包成单独的一个js,然后放在同一的目录下。


我们看一下官方给的demo最终打包出来的目录:

image.png


_snowpack_下放了环境变量相关的文件,_dist_则是我们的业务代码最终打包出来的文件(每一个导出,对应着一个文件),web_modules则是我们的npm依赖映射的js文件,同时会有一个import-map.json来描述npm包与js的映射关系。


vite


vite是针对vue3开发的开发构建工具,从特性的构建流程来看看,vite 和 snowpack 十分接近,从现阶段来说,snowpack更加成熟,对框架的支持更好,而vite肯定对vue3生态的支持契合度更好,但是当前建设不够成熟,毕竟还没有发布正式版本。


比较大的区别是在需要bundle打包的时候Vite 使用 Rollup 内置配置,而 Snowpack 通过其他插件将其委托给 Parcel/webpack。


目前vite在dev的时候HMR使用ESM的方案,当build的时候回使用rollup进行bundle的打包。这个时候就和我们现阶段使用的webpack和rollup没有区别了,可以看出vite的目标非常明确,就是解决开发阶段的构建打包问题,当然这也会带来一个问题就是dev环境和product环境的打包产物几乎完全不一样,造成开发与生产环境构建结果不一致的风险。


总结


尽管ESM已经被几乎所有浏览器所支持,并且snowpack 代表的 bundleless 方案肯定是光明的未来,带来的构建提效非常明显,但是在我们这个还需要考虑兼容,历史包袱的时候仍然不适合直接在生产环境使用的。所以对于这类方案我们还是需要根据实际情况来决定是否使用