Vite 在运行过程中是如何发现新增依赖的?
我们在 《快速理解 Vite 的依赖预构建》[1] 中,已经详细讲述过 Vite 预构建的步骤:
- 1. 依赖扫描,扫描出项目中所有使用到的依赖
- 2. 对这些依赖进行构建
- 3. 在代码运行过程中,将这些模块路径替换成预构建好的产物路径
以上就是一个完整的依赖预构建的流程。但当我们在 Vite 启动后,在编写代码过程中安装了一个新的依赖,并引入到代码中,那这时候 Vite 会怎么处理呢?
这就是本篇文章要聊的内容
引入新依赖后会发生什么?
本文用到的代码在
stackblitz
,在线体验地址[2]
这是项目中唯一的一个 Vue 文件:
<script setup>
import { ref } from 'vue';
// import 'vue-router';
const count = ref(0);
setInterval(() => {
count.value++;
}, 1000);
</script>
<template>
<h5>将 import 'vue-router'; 取消注释</h5>
<h5>页面会刷新,count 会被重置</h5>
<div>{{ count }}</div>
</template>
当我们取消注释,即新引入 vue-router
依赖时(之前没有被使用过),会发现页面刷新了,由于页面刷新,count 会被重置。
我这里只是用了一种比较简单的引入依赖方法,实际上这样引入没有任何意义,仅用于演示。
这里有几个问题,放到后面解答:
- 1. 引入 vue-router 之后,发生了什么?
- 2. 为什么页面会刷新?
- 3. 如果再次注释
vue-router
,又取消注释,页面还会刷新吗?
依赖发现的整个过程
通常 Vite 发现新依赖,是在开发者修改代码并引入新依赖的的时候。
我们就以这种场景为例,分析一下这整个过程。
修改代码会触发热更新,无论是否新增依赖。Vite 热更新的相关知识,我在《Vite 热更新的主要流程》[3]也有详细叙述过,这里做一下总结:
- 1. Vite 监听到
App.vue
被修改 - 2. Vite 通知浏览器重新拉取
App.vue
的代码(其实是通过 websocket 通知 Vite 注入到页面中的@vite/client
,client 负责去拉取代码) - 3. 浏览器重新拉取
App.vue
的代码 - 4. Vite 对
App.vue
重新编译,然后返回给浏览器 - 5. 浏览器运行
App.vue
的热更新逻辑(Vue 框架自带热更新逻辑,在编译时加入的),更新页面

在我们的例子中,新增了 vue-router
依赖。App.vue 会被编译成如下代码(有节选和修改):
// 省略其他引入
// 引入 vue-router 包
import '/node_modules/.vite/deps/vue-router.js?t=1667223198955&v=5e3fbab4';
// App.vue 组件定义
const _sfc_main = {
__name: 'App',
setup(__props, {expose}) {
// 省略,我们组件的 script setup 的内容
}
}
// App.vue 组件的 render 函数,由 App.vue 的 template 模板编译而成
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
// 省略,
}
// Vue 组件热更新代码,Vue 组件编译时加入的
import.meta.hot.accept(mod=>{
// 省略,热更新的具体逻辑
})
// 为 App.vue 组件设置 render 函数
_sfc_main.render = _sfc_render
// 导出处理好的 Vue 组件对象
export default _sfc_main
前后主要新增的是这一行代码:
import '/node_modules/.vite/deps/vue-router.js?t=1667223198955&v=5e3fbab4';
浏览器遇到 import 语句后,就会去 Vite server 请求 vue-router 模块。于是有了以下过程:
- 1. Vite 收到 vue-router 的请求,但发现没有预构建 vue-router,于是 Vite 认为是有新增依赖了
- 2. Vite 重新编译所有依赖,编译完成后 Vite 会通知页面进行刷新
- 3. 浏览器刷新页面
- 4. Vite 此时已经构建好 vue-router,因此能够正常返回内容

为什么构建后需要刷新页面?
要说明白这个,我们得知道依赖预构建,到底构建了什么?输入输出是什么?
依赖预构建的本质
我在《快速理解 Vite 的依赖预构建》[4]详细叙述过构建的输入内容及其输出的产物,这里再总结一下:
实际上,Vite 预构建,本质是一次使用 esbuild 的多入口构建打包的过程,其最终调用的伪代码如下:
import { build } from 'esbuild'
const result = await build({
absWorkingDir: process.cwd(),
// 多个入口
entryPoints: [
// 依赖扫描得到的依赖,即项目中用到的第三方依赖
// 这里假设有 vue、lodash-es、ant-design-vue
'vue', 'lodash-es', 'ant-design-vue'
],
bundle: true,
format: 'esm',
target: [
"es2020",
"edge88",
"firefox78",
"chrome87",
"safari13"
],
splitting: true, // 该参数会自动进行代码分割
plugins: [ /* some plugin */ ],
// 省略其他配置
})
其依赖关系和打包产物如下:

多入口打包,会将各个入口间的公共依赖,抽离成 chunk,因此会得到以下产物:
- 1.
vue
、lodash-es
、ant-design-vue
三个产物的入口文件 - 2. 两个公共代码 chunk 文件
由于预构建的本质上是一次多入口打包,那么每次构建打包产物是不同的
试想以下场景(在线体验地址[5]):
- • 一开始项目只是用了
vue
、ant-design-vue
- • 后来开发者使用
lodash-es
,Vite 需要重新构建

构建前后产物发生了变化,那前面已经拉取的产物文件已经失效,这时候只能刷新页面了
那么这里我们还剩下最后一个问题:再次注释 vue-router
(即不使用新的依赖),页面会刷新吗?
答案是不会,因为 Vite 只会在发现新依赖的时候重新执行构建,那没有发现新依赖,自然就没有接下来发生的重新构建和刷新页面了。
总结
本文用简单的在线例子,来说明 Vite 发现新依赖后的行为。并进一步叙述了,从修改代码,到依赖发现,再到页面刷新的完整流程:
- 1. Vite 监听到代码修改,触发热更新,通知浏览器拉取修改后的模块
- 2. 浏览器请求修改后的模块,新模块中用到了新的依赖,浏览器会拉取新依赖
- 3. Vite 发现该依赖没有被预构建,认为是新依赖,重新执行预构建,并通知浏览器刷新
引用链接
[1]
《快速理解 Vite 的依赖预构建》: https://zhuanlan.zhihu.com/p/561139849[2]
在线体验地址: https://stackblitz.com/edit/vitejs-vite-sxjhy4?file=package.json,src%2FApp.vue&terminal=dev[3]
《Vite 热更新的主要流程》: https://zhuanlan.zhihu.com/p/512365282[4]
《快速理解 Vite 的依赖预构建》: https://zhuanlan.zhihu.com/p/561139849[5]
在线体验地址: https://stackblitz.com/edit/vitejs-vite-ejmcx6?file=src%2FApp.vue,src%2Fmain.js,package.json&terminal=dev