为什么 vite 既用了 esbuild 又用了 rollup ?
esbuild 在开发阶段(vite dev)使用,主要用来预编译第三方依赖和编译业务代码里的 typescript 代码。esbuild 在抹平了第三方依赖的语法差异(第三方依赖不一定是 ESM 的语法)的同时,保证了 vite dev 的开发速度。
rollup 只会在 vite build 的时候执行构建,主要是构建生产环境可稳定使用的包,依然使用 rollup 是因为 rollup 成熟稳定,具有大量优秀的插件。
vite 在开发的时候不进行构建,只是将所有内容转换成 esm 的内容,依赖利用浏览器自己来识别,就和 live server 打开本地 index.html 文件一样。
比如 index.html 导入了一张图片,vite 就让浏览器去磁盘中图片本来的位置引入这个文件。而 webpack 开发时会执行构建,同样引入一张图片,因为构建,这张图片就会放在 index.html 同级的 img 文件夹中。
vite 通过浏览器这种原生识别 esm 的方式来处理依赖,还额外做了什么?
通过浏览器原生来构建可以,但是存在两个严重问题:
vite 在基于浏览器原生支持 esm 的基础上,解决了上述问题,提供了更便捷的方式。
总结:
浏览器原生支持 esm,更准确的说法是浏览器支持模块化,工程化。既然浏览器支持模块化,为什么我们还要将代码构建成 jQuery 开发时代的样子呢?显然是没有必要的。这就是 vite 和 webpack 最大的不同点。
vite 开发时,就像一个增强版的 live server,和我们平时打开 live sever 一样,入口文件是 html,html 里面引入的文件,让浏览器去解析引入。
而 webpack ,html 文件只是一个简单的模板,真正的入口是一个臃肿的 js 文件。开发时,浏览器打开的是前端没有工程化时期的标准项目结构。
|-js
|-img
|-css
|-index.html
正因为 vite 底层是利用浏览器支持 esm 进行工程化的特点,所以 vite 对于 css 文件,图片文件这些,压根不需要什么 loader,因为背后是浏览器来识别,只要浏览器能直接认识这些文件就行。
对于 less,postcss,ts 这些文件,也只要安装一下它们自己对应的处理程序就好,比如 less 的命令行工具,当它把 less 文件转为 css 文件后,浏览器就能识别了。
但是浏览器网络请求中查看请求,请求的文件名称依然是xxx.less
,但是里面的内容其实是 css 内容,而不是 less。想也想得到啊,肯定不是 less ,浏览器压根不认识 less,要真是 less 请求下来了也执行不了。但样式被正确执行了,所以里面肯定是 css 内容。打开文件查看也确实是 css 内容。
这背后的原因是因为对于浏览器中来说,html 才是入口文件,html 中 src 请求的是 less,所以网络请求的当然是 less。但是呢,我们不能让浏览器真去请求 less 文件是吧,所以 vite 实际是转发了请求,让浏览器实际去请求了 less 转换生成的 css 文件。这也是为什么说 vite 是增强版的 live server,因为如果是 live server 肯定傻乎乎的去请求 less 文件了。
这种转发请求的需求非常大,为此 vite 将开发服务器从 1.0 版本的 koa,到 2.0 版本换成了 connect,因为 connect 更适合转发请求。
vite 基于 node,所以需要先安装 node。
安装 vite 工具:
npm i vite -D
局部安装npm i vite -G
全局安装通过 vite 来启动项目:
npx vite
vite 会开启开发服务器后去请求根目录的index.html
文件,并且以该文件作为入口处理依赖。
开发时,vite 会使用 esbuild 将某些依赖预构建成 esm 模块。比如一些第三方包。
另外,在生产构建中则会使用 @rollup/plugin-commonjs 进行构建。
为什么要预构建?有两个原因:
在提升开发性能方面,vite 也使用了缓存。
文件系统缓存
Vite 会将预构建的依赖缓存到 node_modules/.vite。
vite 根据数据源来决定是否需要重新运行预构建步骤:
只有在上述其中一项发生更改时,才需要重新运行预构建。
如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force
命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
浏览器缓存
解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。
如果你想通过本地编辑来调试依赖项,你可以:
--force
命令以重新构建依赖;HTTP 缓存
栗子:
从上面请求可以看到,是从 .vite 中请求预构建好的包,只有一个网络请求。并且 url 中带有 query,表示当前模块版本,当模块更改则会自动请求最新的版本。
并且开发服务器的响应中要求了进行缓存。
页面刷新,同样的模块就是从缓存中被加载,并且并没有发送请求,连请求头都没有。包一旦被缓存,请求不会到达开发服务器。
vite 基于生产 vite build 打包出的项目结构,也是传统的结构,毕竟这种结构才是真正通用的。
对于这种构建产物,想要预览一般使用 live server,但是 vite 内置了这个功能。
npx vite preview
:开启一个本地服务来预览打包后的效果。开发环境⚡️速度的提升
使用JS开发的工具通常需要很长的时间才能启动开发服务器,且这个启动时间与代码量、代码复杂度正相关。即使使用HMR,文件修改后的效果也要几秒钟才能在浏览器中反应出来,代表如Webpack。那么Vite是如何解决如Webpack这样的构建工具一样,在复杂、多模块项目开发中启动慢、HMR慢的问题呢?
我们详细对比了开发环境中的Vite和Webpack,发现主要有如下不同:
Webpack | Vite |
---|---|
先打包生成bundle,再启动开发服务器 | 先启动开发服务器,利用新一代浏览器的ESM能力,无需打包,直接请求所需模块并实时编译 |
HMR时需要把改动模块及相关依赖全部编译 | HMR时只需让浏览器重新请求该模块,同时利用浏览器的缓存(源码模块协商缓存,依赖模块强缓存)来优化请求 |
内存高效利用 | - |
因此,针对开发环境中的启动慢问题:
针对HMR慢:
使用简单,开箱即用
相比Webpack需要对entry、loader、plugin等进行诸多配置,Vite的使用可谓是相当简单了。
只需执行初始化命令,就可以得到一个预设好的开发环境,开箱即获得一堆功能,包括:CSS预处理、html预处理、异步加载、分包、压缩、HMR等。
他使用复杂度介于Parcel和Webpack的中间,只是暴露了极少数的配置项和plugin接口,既不会像Parcel一样配置不灵活,又不会像Webpack一样需要了解庞大的loader、plugin生态,灵活适中、复杂度适中。
Vite 可以使用插件进行扩展,这得益于 Rollup 优秀的插件接口设计和一部分 Vite 独有的额外选项。插件让 vite 功能愈发强大和完善。
配置插件:
// vite.config.js
import legacy from '@vitejs/plugin-legacy' // 插件
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11']
})
]
})
vite 插件
查找插件:
社区中提供了很多插件,可自行查找选择使用。
插件列表
vite.config.js
vite 通过插件对 vue 提供第一优先级支持:
安装:npm install @vitejs/plugin-vue -D
配置插件:
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()]
}
在开发中,我们不可能所有的项目都使用vite从零去搭建,比如一个react项目、Vue项目。这个时候vite还给我们提供了对应的脚手架工具。
所以Vite实际上是有两个工具的:
使用脚手架创建项目:
npm install @vitejs/create-app -g
create-app 项目名称
填好名称,就可以选择想要创建的项目了,比如 vue 或者 react 等等。
官网提供了一种便捷的方式创建项目,它其实就是自动完成了脚手架的安装并使用。
npm create vite@latest
同样填好项目名后就可以选择项目框架了。
对于 vue 项目,可以直接使用npm init vue@latest
创建。
它相当于进一步自动完成了上面的几个步骤,亲儿子果然不一样。