我们在 《快速理解 Vite 的依赖预构建》 中,已经详细讲述过 Vite 预构建的步骤:
- 依赖扫描,扫描出项目中所有使用到的依赖
- 对这些依赖进行构建
- 在代码运行过程中,将这些模块路径替换成预构建好的产物路径
以上就是一个完整的依赖预构建的流程。但当我们在 Vite 启动后,在编写代码过程中安装了一个新的依赖,并引入到代码中,那这时候 Vite 会怎么处理呢?
这就是本篇文章要聊的内容
引入新依赖后会发生什么?
本文用到的代码在
stackblitz
,在线体验地址
这是项目中唯一的一个 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 会被重置。
我这里只是用了一种比较简单的引入依赖方法,实际上这样引入没有任何意义,仅用于演示。
这里有几个问题,放到后面解答:
- 引入 vue-router 之后,发生了什么?
- 为什么页面会刷新?
- 如果再次注释
vue-router
,又取消注释,页面还会刷新吗?
依赖发现的整个过程
通常 Vite 发现新依赖,是在开发者修改代码并引入新依赖的的时候。
我们就以这种场景为例,分析一下这整个过程。
修改代码会触发热更新,无论是否新增依赖。Vite 热更新的相关知识,我在《Vite 热更新的主要流程》也有详细叙述过,这里做一下总结:
- Vite 监听到
App.vue
被修改 - Vite 通知浏览器重新拉取
App.vue
的代码(其实是通过 websocket 通知 Vite 注入到页面中的@vite/client
,client 负责去拉取代码) - 浏览器重新拉取
App.vue
的代码 - Vite 对
App.vue
重新编译,然后返回给浏览器 - 浏览器运行
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 模块。于是有了以下过程:
- Vite 收到 vue-router 的请求,但发现没有预构建 vue-router,于是 Vite 认为是有新增依赖了
- Vite 重新编译所有依赖,编译完成后 Vite 会通知页面进行刷新
- 浏览器刷新页面
- Vite 此时已经构建好 vue-router,因此能够正常返回内容
为什么构建后需要刷新页面?
要说明白这个,我们得知道依赖预构建,到底构建了什么?输入输出是什么?
依赖预构建的本质
我在《快速理解 Vite 的依赖预构建》详细叙述过构建的输入内容及其输出的产物,这里再总结一下:
实际上,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,因此会得到以下产物:
vue
、lodash-es
、ant-design-vue
三个产物的入口文件- 两个公共代码 chunk 文件
由于预构建的本质上是一次多入口打包,那么每次构建打包产物是不同的
试想以下场景(在线体验地址):
- 一开始项目只是用了
vue
、ant-design-vue
- 后来开发者使用
lodash-es
,Vite 需要重新构建
构建前后产物发生了变化,那前面已经拉取的产物文件已经失效,这时候只能刷新页面了
那么这里我们还剩下最后一个问题:再次注释 vue-router
(即不使用新的依赖),页面会刷新吗?
答案是不会,因为 Vite 只会在发现新依赖的时候重新执行构建,那没有发现新依赖,自然就没有接下来发生的重新构建和刷新页面了。
总结
本文用简单的在线例子,来说明 Vite 发现新依赖后的行为。并进一步叙述了,从修改代码,到依赖发现,再到页面刷新的完整流程:
- Vite 监听到代码修改,触发热更新,通知浏览器拉取修改后的模块
- 浏览器请求修改后的模块,新模块中用到了新的依赖,浏览器会拉取新依赖
- Vite 发现该依赖没有被预构建,认为是新依赖,重新执行预构建,并通知浏览器刷新
关联阅读
更多内容可以查看我的专栏:《Vite 设计与实现》
如果这篇文章对您有所帮助,请帮忙点个赞👍,您的鼓励是我创作路上的最大的动力。也可以关注我的公众号订阅后续的文章:Candy 的修仙秘籍(点击可跳转)