Vite 在运行过程中是如何发现新增依赖的?

简介: Vite 在运行过程中是如何发现新增依赖的?

我们在 《快速理解 Vite 的依赖预构建》 中,已经详细讲述过 Vite 预构建的步骤:

  1. 依赖扫描,扫描出项目中所有使用到的依赖
  2. 对这些依赖进行构建
  3. 在代码运行过程中,将这些模块路径替换成预构建好的产物路径

以上就是一个完整的依赖预构建的流程。但当我们在 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 会被重置。

我这里只是用了一种比较简单的引入依赖方法,实际上这样引入没有任何意义,仅用于演示。

这里有几个问题,放到后面解答:

  1. 引入 vue-router 之后,发生了什么?
  2. 为什么页面会刷新?
  3. 如果再次注释 vue-router,又取消注释,页面还会刷新吗?

依赖发现的整个过程


通常 Vite 发现新依赖,是在开发者修改代码引入新依赖的的时候。

我们就以这种场景为例,分析一下这整个过程。

修改代码会触发热更新,无论是否新增依赖。Vite 热更新的相关知识,我在《Vite 热更新的主要流程》也有详细叙述过,这里做一下总结:

  1. Vite 监听App.vue 被修改
  2. Vite 通知浏览器重新拉取  App.vue  的代码(其实是通过 websocket 通知 Vite 注入到页面中的 @vite/client,client 负责去拉取代码)
  3. 浏览器重新拉取  App.vue 的代码
  4. Vite 对  App.vue 重新编译,然后返回给浏览器
  5. 浏览器运行  App.vue 的热更新逻辑(Vue 框架自带热更新逻辑,在编译时加入的),更新页面

1686397523286.png

在我们的例子中,新增了 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,因此能够正常返回内容

1686397469317.png

为什么构建后需要刷新页面?


要说明白这个,我们得知道依赖预构建,到底构建了什么?输入输出是什么?


依赖预构建的本质


我在《快速理解 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 */ ],
    // 省略其他配置
})

其依赖关系和打包产物如下:

1686397431363.png

多入口打包,会将各个入口间的公共依赖,抽离成 chunk,因此会得到以下产物:

  1. vuelodash-esant-design-vue 三个产物的入口文件
  2. 两个公共代码 chunk 文件

由于预构建的本质上是一次多入口打包,那么每次构建打包产物是不同

试想以下场景(在线体验地址):

  • 一开始项目只是用了 vueant-design-vue
  • 后来开发者使用 lodash-es,Vite 需要重新构建

1686397417319.png


构建前后产物发生了变化,那前面已经拉取的产物文件已经失效,这时候只能刷新页面了

那么这里我们还剩下最后一个问题:再次注释 vue-router(即不使用新的依赖),页面会刷新吗?

答案是不会,因为 Vite 只会在发现新依赖的时候重新执行构建,那没有发现新依赖,自然就没有接下来发生的重新构建和刷新页面了。


总结


本文用简单的在线例子,来说明 Vite 发现新依赖后的行为。并进一步叙述了,从修改代码,到依赖发现,再到页面刷新的完整流程:

  1. Vite 监听到代码修改,触发热更新,通知浏览器拉取修改后的模块
  2. 浏览器请求修改后的模块,新模块中用到了新的依赖,浏览器会拉取新依赖
  3. Vite 发现该依赖没有被预构建,认为是新依赖,重新执行预构建,并通知浏览器刷新

关联阅读


更多内容可以查看我的专栏:《Vite 设计与实现》

如果这篇文章对您有所帮助,请帮忙点个赞👍,您的鼓励是我创作路上的最大的动力。也可以关注我的公众号订阅后续的文章:Candy 的修仙秘籍(点击可跳转)


目录
相关文章
|
6月前
|
自然语言处理 JavaScript
【Vue2.0源码学习】模板编译篇-模板解析阶段(整体运行流程)
【Vue2.0源码学习】模板编译篇-模板解析阶段(整体运行流程)
41 0
|
1月前
|
Java
【小技巧】复制一个模块到你的工程(学习阶段很实用)
【小技巧】复制一个模块到你的工程(学习阶段很实用)
|
4月前
|
存储 调度
【源码&库】 Vue3 的依赖收集和依赖触发是如何工作的
【源码&库】 Vue3 的依赖收集和依赖触发是如何工作的
49 0
|
4月前
|
JavaScript 调度
【源码&库】 Vue3 的依赖收集,这里的依赖指代的是什么?
【源码&库】 Vue3 的依赖收集,这里的依赖指代的是什么?
42 0
【源码&库】 Vue3 的依赖收集,这里的依赖指代的是什么?
|
5月前
|
JavaScript 前端开发 Java
项目引入文件的常见报错
项目引入文件的常见报错
46 5
|
8月前
|
JavaScript 开发者
深入vue2.0源码系列:依赖追踪与计算属性的实现
深入vue2.0源码系列:依赖追踪与计算属性的实现
62 0
|
8月前
|
编译器 Shell C++
如何在项目中引入googtest(上)——通过编译器引入库
如何在项目中引入googtest(上)——通过编译器引入库
103 0
|
9月前
|
JavaScript 前端开发
vue3项目创建步骤,以及项目运行失败的原因和解决方法
vue3项目创建步骤,以及项目运行失败的原因和解决方法
519 0
|
9月前
|
Java Maven
springboot依赖分离,本地代码和第三方依赖包分开,减少部署服务时的上传时间
在pom.xml中的<build>标签中的<plugins>标签中添加下面这个插件
|
10月前
|
存储 JavaScript 前端开发
Vite 是如何记录项目中所有模块的依赖关系的?
Vite 是如何记录项目中所有模块的依赖关系的?
164 0