Vite学习笔记(一)Vite的概念和使用
本系列的博客分为三个部分,分别介绍Vite的基本概念与使用,Vite的插件API与插件开发,以及Vite的源码解析。
前端构建工具发展历史
古早时代
最开始的前端开发是没有模块的概念的,那时对于JS的要求是我们写了几个脚本,我们希望这些脚本能够集成到HTML,并且能够满足文件尽量小,HTTP并发数量可控,以及可以做到缓存。所以那时候整体上的工具就是围绕单个文件的操作。
曾经风靡一时的雅虎前端优化10条就包含了上面的这些点,而YUI tools就是那时候的佼佼者,他能够帮助我们压缩js文件并且适当地完成混淆。但很多时候也就仅限于此,那时候前端并没有多少话语权,同时前端的编程能力也较为羸弱,那时候有些前端甚至被称为美工,或者页面仔。对于像HTTP请求的优化,更多其实是由后端服务开发工程师来做的,前端没有权限或者也没有能力来做到。
grunt & gulp
虽然把他们放在一起,但是grunt其实比gulp要早很多,我们可以理解为gulp是grunt的升级版,但总体上他们干的事是一样的。相比于现在的Webpack之类的构建工具,他更多是一个任务管理工具,通过一些列细节的小任务,进行不同的组合来达成一个完整的任务。但是具体任务做什么其实是使用者自己决定的,虽然grunt也提供了一系列工具来进行集成,但是其本身并没有规定任何的模块管理还代码打包的事宜,所以总体上来说构建功能仍然是缺失的。
gulp相比于grunt并没有本质的提升,只是在任务管理方面有了一定的提升,包括任务执行方式,文件流转方式,以及相对应的性能等。所以这边就不再赘述。
browserify
browserify可以称得上是第一代前端构建工具了,他是首个采用了nodejs的模块管理方式–即require,module.exports的模块加载和导出方式–来进行前端项目打包的工具。当你在js代码中使用require(‘./xx.js’)这样的代码,browserify会帮助你去加载对应的文件并且可以把对应的文件进行link和合并,最终产出前端需要的合并文件。
但是相较于webpack,browserify仍然只能打包js,对于css、图片等,你仍然需要依赖当时还很流行的grunt、gulp这样的工具进行处理,你需要为这些文件单独写处理任务,再结合browserify来最终完善整个应用。这一方面在使用体验上无法进行统一,另外其兼容方式并不是那么地完善导致体验并不理想。
也因为此,browserify最终败给了webpack。
webpak & rollup
webpack和rollup本质其实也是js构建工具,但和browserify相比,他们在使用方式做了更全面的设计,其本身就已经是一个核心,并且具有了一定的任务管理的能力。他们都具有插件集成的能力,并且webpack还具有loader的机制来帮助我们应对不同类型文件的引入需求。在这样强大能力的基础上,越来越多原先集中于gulp等任务管理工具的能力都被迁移到了webpack和rollup阵营。现在我们只需要一个命令就可以完成一个大型应用对于各类不同资源的引入需求,webpack能够帮助我们管理代码打包,资源加载,代码压缩等等web前端的大部分需求。
似乎到这里,这个工具已经很完美,我们所有的需求都已经得到了满足,我们对于前端构建还有什么不满意的地方吗?有!主要集中在两个点:
- 配置过于繁琐
- 慢!
如果我们单纯使用webpack来管理项目,那么很可能你的项目里会出现数个不同的webpack配置,他们要么用于不同的环境,要么就是一个文件太多要进行拆分,webpack的配置内容实在关于多,以至于新手一开始完全摸不着头脑。这主要是因为webpack为了满足更多的场景,会不停的增加功能,同时因为webpack是太多的插件了,这些插件也需要学习,就导致其学习成本非常巨大。
而第二点则是有目共睹的,一旦我们的项目达到一定的规模,webpack的启动速度就难以令人满意。上1-2两分钟时非常正常的,大一点的项目比如后台管理,10分钟也是人之常情。另外一点就是热更新的速度,这一点上也不令人满意。
那么这些问题真的能解决吗?能,Vite就是为此而生的。
Vite
Vite采用rollup作为底层,并且大量兼容了rollup的API,其发布初始就具备了大部分的rollup生态能力,所以也不存在Vite功能缺失很多的问题。我们来细数一下Vite是如何解决上述的webpack存在的问题的:
VIte的配置并不繁琐,因为其预设就是为前端项目开发而生,所以对于dev-server,css依赖,图片加载之类的功能其都配置成开箱即用,你完全不需要再去配置插件、loader之类的。所以很可能你的vite配置不会超过20行代码,这在webpack项目中基本不太可能。
而第二点,Vite在开发环境中采用ESM的模块加载方式,并且尽可能剔除了不必要的代码构建,同时采用了激进的Esbuild–一个用go语言开发的JS编译工具,性能是webpack之类的10-100倍–来进行编译提速,这就让Vite启动速度极其迅速,基本3秒内就能就能完成。同时得益于ESM加载方式,Vite不需要提前进行代码打包,所以即便你的项目变得巨大,但是Vite并不关心你的文件规模,所以他的启动速度仍然不会有太多的变化。对于开发时的编译也类似,我们就不在赘述。
Vite简介
这里需要有一点要注意,Vite其实对标或者和Vite同级的概念并不是webpack或者rollup,因为vite并不仅仅是个打包工具,它还内置HMR等功能,而webpack只是个打包工具,HRM要通过webpack-dev-server。
如果非要给vite找一个对标工具,它更像是react的cra。而esbuild对标的才是webpack或者rollup,虽然目前的esbuild看起来更像是个编译工具,比如像babel,但其目标应该是做一个构建工具
接下来提取一些Vite官网的一些介绍。
为什么选用Vite
缓慢的服务器启动
当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
缓慢的更新
基于打包器启动时,重建整个包的效率很低。原因显而易见:因为这样更新速度会随着应用体积增长而直线下降。
一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活[1],但它也仍需要整个重新构建并重载页面。这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热替换(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。这大大改进了开发体验 —— 然而,在实践中我们发现,即使采用了 HMR 模式,其热更新速度也会随着应用规模的增长而显著下降。
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。
一旦你体验到 Vite 的神速,你是否愿意再忍受像曾经那样使用打包器开发就要打上一个大大的问号了。
为什么生产环境仍需打包
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
要确保开发服务器和生产环境构建之间的最优输出和行为一致并不容易。所以 Vite 附带了一套 构建优化 的 构建命令,开箱即用。
为何不用 ESBuild 打包?
Vite 目前的插件 API 与使用 esbuild 作为打包器并不兼容。尽管 esbuild 速度更快,但 Vite 采用了 Rollup 灵活的插件 API 和基础建设,这对 Vite 在生态中的成功起到了重要作用。目前来看,我们认为 Rollup 提供了更好的性能与灵活性方面的权衡。
即便如此,esbuild 在过去几年有了很大进展,我们不排除在未来使用 esbuild 进行生产构建的可能性。我们将继续利用他们所发布的新功能,就像我们在 JS 和 CSS 最小化压缩方面所做的那样,esbuild 使 Vite 在避免对其生态造成干扰的同时获得了性能提升。
Vite中是否有Loader的概念
Vite 自身不包含 loader 的概念。
Vite 通过原生 ES 模块导入提供了开箱即用的支持,能够直接导入 .js
、.jsx
、.ts
、.tsx
、.css
、.json
等文件,不需要额外的 loader 配置。
对于其他类型的资源文件,Vite 提供了两种处理方式:
使用专门的插件,比如
@vitejs/plugin-vue
来处理.vue
文件,unplugin-auto-import
来自动导入 API。这些插件会在内部使用 loader 做转换和加载。使用
vite.transform
钩子对某些文件类型提供自定义的转换链。这相当于 loader 的功能,但需要自行实现。
另外,Vite 也支持在 vite.config.js
中使用 Rollup 的 load
钩子来加载自定义文件类型。
所以综合来说,Vite 本身不支持 loader,但通过插件和钩子可以实现类似 loader 的转换和加载能力。Vite 倾向于使用原生 ES 模块和插件来处理资源,而不是直接使用 loader。
总结
简单来说,vite借用了以下几个已有的功能:
- 新版本浏览器的支持esm
- esbuild的高效打包能力,将代码及其依赖的第三方包中非esm的代码打包成esm
- rollup的打包能力和成熟的插件系统
那么vite是怎么利用这几个功能的呢?
- 开发环境下(默认开发者用的都是最新的支持esm加载的浏览器),自己实现和HMR功能,并且分析esm之间的依赖,去将esbuild打包出来的esm供浏览器使用,提高了开发环境下的的热更新效率和速度
- 生产环境下,还是使用rollup的打包功能,并且可以直接使用rollup的插件社区
- 内置了许多常用的前端开发用到的插件到vite中,做到开箱即用
实践
如何使用vite构建一个react项目
vite对react的支持优先级是高于vue2的,可以开箱即用,使用npm命令就可以了
1 | yarn create @vitejs/app |
该命令会创建一个代码模板,并在vite.config.js中使用react-fastRefresh插件
vite中如何加载静态资源
这个直接看官方文档就可以,在上一步创建的项目中稍加改动就好。