vite之no-bundle构建方案初探

Daotin 于 2023-03-15 发布 编辑

背景

在现代前端项目中,都需要构建这个环节,以 webpack 为主的构建工具无疑占据着主流地位。其核心思想是从一个或多个入口文件开始,根据入口文件递归寻找出依赖文件,然后构建出一个依赖图,最后将你项目中所需的每一个模块组合成一个或多个 bundles,然后在浏览器中加载处理好的 bundles

使用 webpack 构建开发的好处有很多,比如模块化开发,构建时可以进行代码压缩、代码分离、文件合并、图片压缩等功能,可以有各种 loader 对资源进行管理,代码热更新增加了开发体验等。

但是缺点就是构建时间长,项目越大,构建速度就会越慢。一些大型项目可能构建一次要花上十分钟,太浪费时间了。

因此就有了无包构建no-bundle的诞生。

什么是 no-bundle 构建

no-bundle 构建的思想是在构建的时候只处理模块的编译而无需打包,把模块间的依赖关系交给浏览器处理。由于现代浏览器本身就支持 ES Module,会自动向依赖的 Module 发出请求。no-bundle 充分利用这一点,简化构建时需要处理的过程,提升构建效率。

一提到 no-bundle 构建工具,第一个想到的就应该是 ViteVite 是一个 基于原生 ES-Module 的前端构建工具

Vite 主要从以下两方面提升开发体验:

也就是在开发环境使用浏览器自带的模块处理能力处理构建过程,只编译不打包,而在生产环境下再使用 Rollup 打包的这种方式,理论上即提高了开发效率,也能够保证生产环境的代码要求。

Vite 原理

在我们开发的时候,Vite 主要做的以下三步内容:拦截请求、替换内容、解析返回结果。

拦截请求:在项目启动的时候,Vite 首先会先启动一个 koa 服务器拦截浏览器发出的 ESM 请求,使用 ES Module Lexer 进行处理,Lexer 会找到代码中的 import 语句导入的模块然后以数组的形式返回。然后 Vite 会根据不同的请求类型做不同的处理,最终将处理过的 ESM 的代码返回给客户端。

Vite 在对请求进行处理的时候,将代码分为依赖源码两部分。

对于依赖部分,Vite 使用 Esbuild 对依赖进行编译和代码转换(将不是 ESM 的依赖转换为 ESM ),然后将编译结果缓存起来,因为依赖包在开发中大部分情况是不会改变。 而 Esbuild 有以下优势。

  1. 语言优势,Esbuild 使用 Go 语言开发,相对于 JavaScript,Go 语言是一种编译型语言,在编译阶段就已经将源码转译为机器码。
  2. 多线程,Rollup 和 webpack 都没有使用多线程的能力,而 Esbuild 在算法上进行了大量的优化,充分的利用了多 CPU 的优势。

以上这些原因,导致 Esbuild 构建模块的速度比 webpack 快到 10-100 倍。

这个时候,有的同学可能会有疑问了,Vite 不是号称 no-bundle 是不需要构建的,那为什么还会有依赖预构建呢?

Vite 确实号称是一种 “no-bundle” 工具,即不需要像传统的打包工具一样将所有的代码打包成一个或多个文件,而是在开发过程中根据需要动态地生成代码,并使用浏览器原生的 ES 模块系统进行加载,从而提高应用的开发和调试效率。

然而,即使使用 Vite 进行开发,我们仍然需要使用一些第三方依赖项来构建我们的应用,例如 Vue、React、Axios 等等。这些依赖项通常是通过 NPM 或 Yarn 安装的,而且它们常常比我们自己编写的代码更复杂,包含了大量的 JavaScript、CSS 和其他资源文件。

由于这些依赖项比较复杂,而且通常是由其他开发者编写的,因此在加载和执行时可能会比我们自己编写的代码慢一些。为了提高应用的加载速度,我们可以使用 Vite 的预构建依赖项功能,将一些常用的依赖项提前打包成单独的文件,在应用加载时提前加载这些文件,从而避免了在应用运行时再去下载这些依赖项,提高应用的加载速度。

需要注意的是,预构建依赖项并不是构建应用的必要步骤,而是一种优化手段。如果你的应用规模很小,依赖项比较少,或者你不需要优化加载速度,那么你可以不使用预构建依赖项。但是如果你的应用规模比较大,依赖项比较多,或者你需要优化加载速度,那么预构建依赖项可能会对你的应用产生积极的影响。

对于源码部分,通常会使用 ESModules 或者 CommonJS 拆分到大量小模块中。

源码部分又包括源代码和静态资源。

Vite 在浏览器发送对模块的请求时,拦截请求,对源码进行转换(包括依赖 imports 替换,静态资源 import 替换,*.vue 文件的替换等)后提供给浏览器,实现了源码的动态导入。

具体的源码解析参见:https://juejin.cn/book/7053736179887243267/section/7063027498438623232