最近基于 ES module 的 bundleless 构建工具很火,特别是尤大在到处推广 Vite ,号称 “下一代前端构建工具”,在前不久的 VueConf 2021 上,尤大可谓狠狠地宣传了一下。以至于一度有很多人认为,有了 Vite 之后就不再需要 Webpack 了。虽然 Vite 现在确实很火,也有很多人在尝鲜,但现实恰好与大家的观念相反,目前几乎没人把 Vite 用于实际的项目开发。

img

首先先要先明确一个问题,为什么 Webpack 需要打包?个人认为主要有两点原因:第一,在过去由于 HTTP/1.1 受网络延迟和浏览器并行请求限制等原因,将资源合并压缩有助于减少 HTTP 请求从而提升页面加载性能。第二,JS 本身并没有提供模块系统。虽然 Node.js 中提供了 Common.js 的模块化机制,但是浏览器环境并不支持。

而且虽然 ES2015 标准中提供了语法层面的模块化规范,但是现在仍然是 implementation-defined 的状态,无论是 Node.js 还是浏览器均不支持(使用 babel 还是会编译为 Common.js)。直到 2017 年 Chrome 61 发布,浏览器才具有了对 JavaScript Module 的原生支持。因此,使用 Webpack 显然好处不只是一点点,不仅可以对静态资源打包压缩,还实现了一套模块系统,另外还有各种 loader、plugin ,可以对打包的代码进行 polyfill ,还可以编译转换 .less 、.vue 、.jsx 等在浏览器无法识别的文件格式,更重要的是提供了 HMR 机制,极大地提升了开发体验。

当然 Webpack 的痛点也不少,由于 Webpack 在打包时需要构建模块依赖图,因此需要递归遍历所有的模块,最明显的就是随着项目体积的增长,构建的时间也线性增长,启动时间也相应增加。

第二,当我们修改了其中的文件之后,Webpack 需要重新打包。

第三,由于 Webpack 在打包后会对源码进行编译和压缩,代码的可读性大大降低,因此在开发环境下,我们需要借助 SourceMap 进行调试,而开启 SourceMap 无疑也会降低打包速度。

第四,随着项目体积增加,打出来的包也越来越大,过大的包会延长页面首屏加载时间,对此 Webpack 也有解决方案,即使用 Tree Shaking、lazy-loading、CommonsChunkPlugin 等。

# 那么为什么现在开始尝试不打包了?

首先最主要的原因是 HTTP/2.0 实现了多路复用和首部压缩,解决了 HTTP/1.1 中队头阻塞的问题,因此通过资源合并压缩减少 HTTP 请求对页面加载性能不会有显著提升了。其次,随着 Chrome 61 发布,现在各大浏览器逐一支持 ESM ,直接浏览器便可解析 imports ,无需自己再实现模块化了。通过浏览器原生 ESM 最大的好处就是可以实现按需加载,不用打包了,构建时间复杂度 O (1),启动很快,只需要启动 devServer 即可。

img

和 Snowpack 类似,Vite 也是利用浏览器原生的 ESM 去解析 imports ,然后向 devServer 请求模块,在服务器端使用 esbuild 对模块进行即时编译后返回(主要是转换 .jsx 、.tsx 、.vue 、.less 等浏览器无法处理的文件,而不是编译为 ES5 兼容老旧浏览器)。

# 那么为什么 Vite 使用 esbuild 进行编译?

一个字:快!我们可以看一组对比,Webpack5 打包需要 54.5s,而 esbuild 仅需 0.37s。这是因为 esbuild 是用 Golang 编写的,自然比用 JS 编写的构建工具快很多。同时,esbuild 中的算法经过精心设计,大量使用了并行操作,可以充分利用 CPU 资源。另外,esbuild 没有使用第三方依赖,和内存的高效利用,也是打包快的原因。

img

尽管 esbuild 优势十分明显,但是它仍然无法取代 Webpack ,主要有两点原因:第一,esbuild 虽然有 loader ,但是没有插件机制;第二,esbuild 没有热更新。那么 Vite 在 esbuild 的基础上解决了热更新的问题,是不是就可以干掉 Webpack 了。

# 现在回到开头那个问题,为什么现在几乎没人把 Vite 用于实际项目的开发?

首先一个就是,虽然 Vite 在开发环境使用原生 ESM,但是在生产环境仍然需要使用 Rollup 打包。因为嵌套 import 会导致发送大量网络请求,即使使用 HTTP/2.0 ,直接使用未打包的 ESM 仍然效率低下。所以为了在生产环境中获得最佳的加载性能,最好将代码 bundle 一遍(结合 Tree Shaking,lazy-loading 和 common chunk splitting 等技术手段)。

所以现在看来,Vite 更像是一个开发工具,而不是用于生产环境的构建工具。另外,开发环境使用浏览器 ESM 解析模块,而生产环境使用 Rollup 打包,这就导致本地和线上环境跑的代码不一致,线上环境出了 bug,本地很难排查。那有的同学会问,为什么 Vite 不用 esbuild 打包?esbuild 速度非常快,已经是一个非常强大的 Bundler,但一些构建应用所需的重要特性仍在开发中 —— 特别是 代码分割 和 CSS 处理 。目前,Rollup 在这些方面更加成熟和灵活。也就是说,当 esbuild 在将来稳定这些特性时,不排除在生产版本中使用 esbuild 的可能性。

综上,Vite 其实和 Vue-cli 一样,可以说是 Vue 官方给个人开发者和中小企业提供的脚手架工具,毕竟不用自己造轮子了。正如 Vue-cli 就是对 Webpack 的封装一样,Vite 其实就是对 esbuild 的封装,因此 “Vite 能干掉 Webpack” 这句话本身就是不对等的,充其量作为下一代脚手架工具还差不多。

而且 Vite 现在还只能在开发环境用 ESM ,生产环境还是需要打包,基本上只有浏览器支持完全和基于 QUIC 协议的 HTTP/3 普及之后才可能大规模使用 ESM 。更何况 Webpack 发展至今 loader 和 plugin 生态已经十分丰富,而 bundleless 构建工具的生态还在起步阶段。长远来看 ESM 确实极具发展潜力,不排除以后 bundleless 构建工具可能会超过 Webpack ,但是 Webpack 在现在的前端工程化中仍然扮演着非常重要的角色。