Vite 是什么?
在此之前先贴一个Vite 官中文档
概述
Vite 基于 ESM(ES modules) 以肉眼可见的迅速进行开发模式下的模块热更新(HMR),在生产模式下使用 Rollup 打包代码。
如果你不了解 ESM,可以看看 模块化。
项目搭建
我们这次分析的 Vite 版本为 2.3.7
。
首先常规操作,创建一个 Vite 项目:
npm init @vitejs/app
项目创建好,安装好了依赖,运行项目,我们在控制台中可以看到 index.html
中的代码:
可以看到 Vite 确实是依赖 ESM 进行模块化的。
流程
Vite 配置并启动了一个 Koa 和 WebSocket 服务。WebSocket 服务是用来进行 HMR 热更新的,这个等下会说到。而 Koa 服务则拦截浏览器对 ESM 的请求,将对应路径下的文件做一定处理并返回给客户端。
预构建
在启动 Vite 时,Vite 会对项目中所有依赖进行预构建。
由于 Vite 是使用 ESM 的形式导入模块,因此需要先将 CommonJS 或 UMD 形式的依赖转换为 ESM 形式。第二就是将多模块整合成单模块,通常情况下一个包包含许多内置模块,引入的时候如果不做处理,浏览器会发出许多个请求导致页面加载变慢。
缓存
在这之前,先了解一下 Vite 的文件缓存系统。预构建的依赖会被缓存到 node_module/.vite
中,只有在特定情况下如 package.json
依赖项改变时才会重新进行预构建。
node_module
中的依赖是强缓存的,因此单纯的修改依赖项文件是不会触发热更新的,可以删除 node_module/.vite
或者在命令行使用 --force
命令启动开发服务器来强制更新。
路径处理
我们在浏览器控制台查看各个模块的请求路径时会发现,Vite 会对我们请求的依赖进行路径处理:
比如高亮的这一行 Vue,Vite 会将依赖 Vue 的路径改成 node_modules/.vite/vue.js?v=hash
,node_module/.vite
也就是 Vite 的预构建缓存的位置。除了 node_module
中的依赖项,其他路径都是正常的项目路径。
对于 .vue
文件,Vite 会进行进一步处理,就拿 App.vue
来说,这是原本的代码:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
这是处理后的代码:
import { createHotContext as __vite__createHotContext } from '/@vite/client'
// HMR
import.meta.hot = __vite__createHotContext('/src/App.vue')
// 引入子组件
import HelloWorld from '/src/components/HelloWorld.vue'
const _sfc_main = {
expose: [],
setup(__props) {
return { HelloWorld }
},
}
import {
createVNode as _createVNode,
createTextVNode as _createTextVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createBlock as _createBlock,
} from '/node_modules/.vite/vue.js?v=a25af474'
// 创建虚拟node节点
const _hoisted_1 = /*#__PURE__*/ _createVNode(
'img',
{
alt: 'Vue logo',
src: '/src/assets/logo.png',
},
null,
-1 /* HOISTED */
)
// 渲染节点
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createBlock(
_Fragment,
null,
[_hoisted_1, _createVNode($setup['HelloWorld'], { msg: 'Hello Vue 3 + Vite' })],
64 /* STABLE_FRAGMENT */
)
)
}
// 引入App.vue的样式表
import '/src/App.vue?vue&type=style&index=0&lang.css'
// 省略
'/src/App.vue?vue&type=style&index=0&lang.css'
内容:
import { createHotContext as __vite__createHotContext } from '/@vite/client'
// HMR
import.meta.hot = __vite__createHotContext('/src/App.vue?vue&type=style&index=0&lang.css')
import { updateStyle, removeStyle } from '/@vite/client'
const id = 'E:/personal web/vite-analysis/vite-project/src/App.vue?vue&type=style&index=0&lang.css'
// style
const css =
'\n#app {\n font-family: Avenir, Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-align: center;\n color: #2c3e50;\n margin-top: 60px;\n}\n'
updateStyle(id, css)
import.meta.hot.accept()
export default css
import.meta.hot.prune(() => removeStyle(id))
因此 Vite 实际上将 Vue 文件分成了两个部分,一个是处理 HTML 和 JS 的代码,一个是处理 CSS 的代码。
在我们修改样式的时候,WebSocket 服务检测到改变,发送消息给客户端,浏览器会重新请求新的样式,请看下图:
可以看到更新样式后,WebSocket 发送了一个 message,里面包含了更新的路径以及时间戳。浏览器会发送这样的请求:http://localhost:3000/src/App.vue?import&t=1623750496994&vue&type=style&index=0&lang.css
里面就包含了路径和时间戳,时间戳可以确保唯一性。
但如果我们修改的是 HTML 或 JS,则会直接请求 App.vue
:
因此,在我们修改了 App.vue
的 HTML 或 JS 代码后,浏览器会发送两个请求:/src/App.vue
和 '/src/App.vue?vue&type=style&index=0&lang.css'
。
静态资源处理
Vite 会对引入的静态资源如图片,样式文件等路径做处理(包括 CSS 中的 url()
引入);在开发模式下正常路径引入,在生产模式下会进一步处理,如:
import img from './assets/logo.png'
打包之后路径就变成了 /assets/logo.03d6d6da.png
。
当然,这看上去和之前用 Webpack 打包是类似的,但区别在于 import
的时候既可以使用开发期间的项目根路径,也可以使用相对路径。
热更新 HMR
之前我们说过,Vite 的 HMR 是通过 WebSocket 实现的。结合上面的一些例子我们也能得出一个结论:服务端监听代码文件的改变,通过 WebSocket 向服务端发送更新文件的信息通知客户端发送新的请求来更新代码。
同时,Vite 也暴露了一些 HMR API 供开发者使用。
ESBuild
Vite 在开发环境下使用的是 ESBuild
进行依赖预构建,最大的原因就是 ESBuild
实在是太快了速度远远超过 Rollup
, 更不要说 Webpack
。
ESBuild 为什么那么快?
根据这张图的数据不难发现,无论是构建 TS 还是 JS,ESBuild
都有着一骑绝尘的优势。
在 ESBuild 官网上,开发者明确的说明了为什么 ESBuild
如此之快:
ESBuild
是使用 GO 编写的,并且由于 GO 的并行性,线程之间也可以共享内存,因此在速度上相比 JavaScript 有着极大的优势。ESBuild
会充分利用所有可用的内核确保效率最大化。ESBuild
为了避免使用第三方工具带来的性能损失,全部从 0 开始编写。- 有效利用内存,最大限度地重用数据。
生产环境下仍然使用 Rollup
截至目前:2021-6-16,由于 ESBuild
暂时还不太稳定,因此 Vite 没有将它用于生产构建,不过相信在不久地将来一定会实现。
支持 React
Vite 虽然是 Vue 的作者开发的,但是不仅支持 Vue,其他框架如 React 也支持,甚至支持 SSR(服务器渲染)。
Webpack 和 vite 的区别
webpack 会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。 而 vite 是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
由于现代浏览器本身就支持 ES Module,会自动向依赖的 Module 发出请求。vite 充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像 webpack 那样进行打包合并。
由于 vite 在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite 的优势越明显。
在 HMR 方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像 webpack 那样需要把该模块的相关依赖模块全部编译一次,效率更高。当需要打包到生产环境时,vite 使用传统的 rollup 进行打包,因此,vite 的主要优势在开发阶段。
最后
更多的有关 Vite 的说明,配置等,请看 Vite 官中文档