背景
自从尤大大的 vite 问世后,现在前端的构建工具由 webpack 转向 vite 的越来越多,今天主要来讨论一下 vite 的一些工作原理,讨论之前大家可以看这篇文章,尤其是注意其中谈到的 Bundleless,这样也能更好的理解 vite。
前端构建工具大盘点:gulp、webpack、vite、rollup、esbuild、snowpack、babel、parcel、swc、tsc
毫无疑问,由于 EMS 的出现,Bundleless 是未来的趋势,而 vite 基本上是 Bundleless 的一个代表。
两种模式
Vite分为开发模式和生产模式
开发模式
Vite提供了一个开发服务器,然后结合原生的ESM,当代码中出现import的时候,发送一个资源请求,Vite开发服务器拦截请求,根据不同文件类型,在服务端完成模块的改写(比如单文件的解析编译等)和请求处理,实现真正的按需编译,然后返回给浏览器。请求的资源在服务器端按需编译返回,完全跳过了打包这个概念,不需要生成一个大的bundle。服务器随起随用,所以开发环境下的初次启动是非常快的。而且热更新的速度不会随着模块增多而变慢,因为代码改动后,并不会有bundle的过程。
Vite Server 所有逻辑基本都依赖中间件实现。这些中间件,拦截请求之后,完成了如下内容:
- 处理 ESM 语法,比如将业务代码中的 import 第三方依赖路径转为浏览器可识别的依赖路径;
- 对 .ts、.vue 等文件进行即时编译;
- 对 Sass/Less 的需要预编译的模块进行编译;
- 和浏览器端建立 socket 连接,实现 HMR。
生产模式
利用Rollup来构建源代码。
Vite将需要处理的代码分为了两大类
第三方依赖:
这类代码大部分都是纯JavaScript,而且不会怎么经常变化,Vite会通过pre-bundle的方式来处理这部分代码。Vite2使用esbulid来构建这部分代码,esbuild是基于go的,处理速度会比用JavaScript写的打包器要快10-100倍,这也是Vite为什么在开发阶段很快的一个原因。
业务代码:
通常这部分代码,都不是纯的JavaScript(例如:JSX,Vue等),经常会被修改,而且也不需要一次性全部加载(可以根据路由,做代码分割加载)
由于Vite使用了原生的ESM,Vite本身只需要按需编译代码,启动静态服务器就可以。只有当浏览器请求这些模块,这些模块才会被编译,动态加载到当前页面中。
热更新原理
就是在客户端与服务端建立了一个 websocket 连接,当代码被修改时,服务端发送消息通知客户端去请求修改模块的代码,完成热更新。
其他优化
缓存
https://cn.vitejs.dev/guide/dep-pre-bundling.html#caching
- Vite会将pre-bundle的依赖存放在
node_modules/.vite
下,如果想修改目录,可以在 vite 文档中搜索 cacheDir
- 浏览器缓存 Vite Dev Server 会将这些第三方依赖设置HTTP强缓存来提升性能,只有当这些依赖发生变化时,才会去更新query id使之前的的缓存失效。
使用esbuild依赖预构建
依赖预构建仅会在开发模式下应用,并会使用 esbuild 将依赖转为 ESM 模块。在生产构建中则会使用 @rollup/plugin-commonjs。
https://cn.vitejs.dev/guide/dep-pre-bundling.html#dependency-pre-bundling
Q&A
为什么生产环境还是需要bundle?
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
为何不用 ESBuild 打包?
虽然 esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。尽管如此,当未来这些功能稳定后,我们也不排除使用 esbuild 作为生产构建器的可能。
参考
https://cn.vitejs.dev/guide/why.html