前言
构建工具作为前端开发工作中非常重要的一环,与我们的开发工作息息相关。构建工具发展至今,社区已经有了很多非常成熟的解决方案,Webpack 凭借大而全的能力、丰富的社区生态依旧牢牢占据构建天下的半壁江山;Rollup 基于 ESM 模块规范进行打包,率先提出支持了 Tree Shaking 打包,成为了最流行的开源库打包工具;Esbuild 因为其快一个数量级的构建速度也在分得一杯羹。不过这些都不是我们今天要聊的,在这些打包工具中,可以看到有一个小伙子的 npm 下载量在稳定上升中,这就是我们今天的主角,新一代的前端构建工具 Vite,7月份刚发布了 v3 版本,下面就来一起聊一聊 Vite 吧。
Vite 简介
一个想法
Vite 是什么?
-
一个开发服务器,它基于原生 ES 模块提供了丰富的内建功能,如速度快到惊人的模块热更新。
-
一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。
Vite 相较于传统的 Webpack、Rollup 的编译方式,采用的是 ESM 混合编译。即在开发环境,使用 Server 动态编译 + 浏览器的 ESM ,实现了开发环境 “0编译”。在生产环境时,采用了 Rollup 进行打包编译。
Vite 在线体验
点击 vite.new/react 即可在线使用 Vite 。
Vite 快!
Vite 的法语意为"快速的",顾名思义,Vite 非常快,有快自然是有对比,那么来看下为啥传统打包工具如 Webpack 慢。
- Webpack 需要先对整个项目文件进行依赖解析、打包构建后再启动开发服务器,启动速度随着项目文件越来越多而变慢,当我们修改 Bundle 中的一个子模块,整个 Bundle 都会被重新打包,虽然现在 Webpack 在 HMR 方面有许多优化,但整个过程随着项目复杂性增高还是比较费时。
-
Vite 以原生 ESM 方式服务源码,当 import 模块时,浏览器通过 http 请求下载导入的模块。启动开发服务器,代码执行到页面对应的模块时自动请求模块文件,实现自动按需加载。在项目规模膨胀之后,即使增加了很多 route,也不影响启动加载速度。
-
不需要打包过程
-
自动按需处理
-
可利用浏览器缓存
-
可实现高效的热更新
-
依赖预构建
什么是依赖预构建?
vite将代码分为两部分:
-
依赖:例如react、antd,纯JS在开发中不会变动的部分
-
源码:业务代码,非纯JS代码(需要转换JSX/LESS)并不是所有都需要被加载,开发时经常变化
在首次启动 Vite 时,会使用 Esbuild 将依赖重新构建一遍并缓存在 node_modules。
目的
-
浏览器不支持的 CommonJS 或 UMD 的依赖项转换成 ESM
-
有许多内部模块的 ESM 依赖关系转换为单个模块,保证一个依赖最多对应一个 http 请求,提高后续加载性能
-
构建的依赖缓存到 node_modules/.vite,解析后的依赖会设置 HTTP 头 max-age=31536000,immutable进行强缓存
3.0优化
与 2.x 版本相比, Vite 3.0 在冷启动阶段进行了优化。在 2.9 版本前,Esbuild 会扫描项目中的依赖,进行一次打包,然后启动 dev server。
存在的问题:
-
依赖预构建会阻塞 dev server 的启动,但实际上两者没有依赖关系
-
存在二次预构建的可能,同时导致页面 reload
-
Plugin 在运行过程中动态给源码注入新的第三方依赖
-
Vite 插件手动注入 import 语句,比如调用 babel-plugin-import 添加import Button from 'antd/lib/button',因为 antd/lib/button 的引入代码由 Vite 插件注入,属于 dev server 运行时发现的依赖,冷启动阶段无法扫描到。
-
-
动态依赖在代码运行中才能确定最终的 url。
- 动态引入某个 utils,同时 utils 里面可能引入其他依赖
const importModule = (m) => import(`../utils/${m}.js`);
-
二次预构建的过程不仅需要将所有的依赖全部进行预构建,而且由于依赖的更新,页面需要 reload 加载最新的代码,导致 dev server 的性能下降,开发的体验也不好。
2.9 版本 Vite 对这部分逻辑进行了重构,解决了部分问题:
-
dev server 启动后预构建阶段在后台执行,也就是预构建不再阻塞 dev server 的启动,真正做到服务秒启动的效果
-
如果某些依赖在 dev server 运行中发现的, Vite 会尽可能复用已有的构建产物,不进行全量构建
具体实现可以看这个 PR
但是 2.9 版本并没有解决二次预构建页面 reload 的问题,但是在 3.0 的版本中得到了解决。
3.0 通过延迟预构建的过程来实现,当浏览器请求第三方依赖时, dev server 会将首屏期间所有涉及到的所有模块依赖关系全部解析完后,才给浏览器发送 response。因此,如果发现有未预构建的第三方依赖,这个第三方依赖的请求会阻塞直到预构建完成。本质上是通过消耗首屏性能交换 reload 交互体验。
实际上 Vite 3.0 并没有解决懒加载二次构建导致的 reload 问题,目前社区提供了 提供的 vite-plugin-optimize-persist、vite-plugin-package-config 插件进行解决。
Vite 的缺点
首屏性能
先来看下 Webpack 的首屏渲染流程,浏览器向 dev server 发起请求,dev server 接收到请求后,直接将打包构建好的首屏内容返回给浏览器。
那么 Vite 相比较 Webpack 额外做了哪些工作呢?
-
需要通过 http 请求加载模块文件
-
动态加载的文件需要进行转换解析工作
-
预构建、二次预构建阻塞请求,直接预构建完成
和 Webpack 相比,Vite 把 Webpack 的 dev Server 启动过程中完成的工作放在了 dev server 和响应浏览器请求中,不过即使额外做了这些工作,首屏渲染时间相较于 Webpack 的构建时间依旧有着极大的优势。之后在页面 reload 时,之前完成转换的内容已经进行了缓存,因此后续加载速度会快很多。
-
Webpack 首屏渲染
-
Vite 首屏渲染
懒加载性能
原因同首屏性能。
Dev 和生产不一致
Vite 在开发环境使用 Esbuild 对代码进行构建,但是生产环境使用的却是 Rollup。
Esbuild 在以下几个场景难以处理:
-
自动 CSS 代码分割
-
对异步懒加载请求自动优化
-
手动的代码分割控制
由于生产环境的构建场景和开发阶段存在天然差异,所以不可避免的生产环境的表现会和开发环境不一致。要解决这个问题,需要保证生成环境依赖处理规则和开发环境一致,也使用 Esbuild 进行打包,目前该 feature 已经实现,并且在 Vite 3.0 版本作为一个实验性功能上线,可以通过添加optimizeDeps.disabled: false 配置开启。
未来展望
Vite 确实能实实在在的提升开发者的开发体验&效率,但在稳定性和兼容性上目前仍然有很长一段路要走。目前 Vite 团队决定至少每年发布一个新的 Vite 主要版本,以与 Node.js 的 EOL 保持一致。Rollup 将在接下来几个月发布 v3 的大版本,距离 Rollup 2.0 发布已经过去2年多了,由于 Vite 的架构上也非常依赖 Rollup,因此在 Rollup 发布 v3 版本后,Vite 也会进行快速跟进一个新的主要版本,这是另一个在今年引入更重大变化的机会,Vite 会利用这些变化来稳定此版本中引入的一些实验性功能。
Vite 的版本更新迭代速度是非常快的,几天就有一个新的小版本发布,也可以看到越来越多的开发者提issue、PR参与到这个项目,相信在不久的将来 Vite 能够被越多越多的公司&团队所使用。
参考文章
-
-
面向未来的前端构建工具-vite
-
Vite 3.0 正式发布,下一代前端构建工具!
-
深入理解Vite核心原理
-
为什么有人说 vite 快,有人却说 vite 慢?
-
Vite 3.0 核心更新调研