我修复了一个 Vite Bug,让我的项目首屏性能提高了 25%

news2025/7/22 14:02:08

本文正在参加「金石计划 . 瓜分6万现金大奖」

一次偶然的机会,我将项目(基于 tdesign-vue-next-starter )由 Vite 2.7 升级成 Vite 3.x 后,发现首次运行 Vite dev 构建,页面首屏时间非常长,且一定会整个页面刷新一次。而第二次进入则不再刷新页面。

充满好奇心的我,决定研究一下为什么 Vite.3.x 会有这么一个负优化,于是我仔细研究源码,最终发现了问题的根源,并给 Vite 提交了修复的代码

大概测了一下,修复前的页面首屏时间为 1m06s,修复后为 45s,性能提升了 25%

问题详情

升级 Vite3.x 后的代码放到了该仓库,感兴趣的同学可以自行调试

项目升级 Vite3.x 后,首次进入页面,页面的首屏时间非常的长,且一定会刷新整个页面,这个问题只有在没有 Vite 缓存情况下出现。

因为我们可以通过以下方式复现:

vite --force
复制代码

从日志中,可以初步判断出,Vite 在运行过程中,发现了新的依赖,然后重新执行预构建,再刷新页面。

因此我们需要更多的信息,要打印更多的运行 log,以清楚 Vite 的运行状态。这里我们可以通过设置 DEBUG 环境变量,来输出更多的关于依赖构建相关的日志:

# vite:deps 是指过滤出依赖预构建的日志
# force 代表不使用之前构建的缓存,以确保每次都能复现问题
cross-env DEBUG=vite:deps vite --force
复制代码

运行结果如下:

我们来仔细看一下日志信息:

仅仅从日志的字面意思,我们可以得出以下信息:

  1. Dev server 启动
  2. 依赖扫描,扫描出了项目中使用了哪些依赖。这里扫描到的依赖是不全的
  3. 访问页面后,发现新的依赖(lodash/union),重新执行依赖构建
  4. 发现新的依赖(echarts/charts、echarts/renderer 等),又重新执行依赖构建
  5. 刷新页面

看起来就是因为依赖扫描的时候,有很多依赖没有被扫描出来,那么这些依赖没有被预构建。导致运行代码时,多次发现新的依赖(没有进行预构建),导致又要重新执行预构建,最后还刷新了页面。

因此可能问题的根源是:Vite 的依赖扫描没有扫描到所有的依赖

Vite 的依赖扫描

这块涉及到 Vite 依赖扫描的相关知识,恰好之前就研究过这个内容,还写了一篇文章:《五千字深度解读 Vite 的依赖扫描》,这里总结一下:

  1. 用 esbuild 打包一遍整个项目
  2. 打包过程中遇到 import 语句,就把 import 的内容记录下来,例如 import Vue,就记录 Vue 到数组中
  3. 最后只留下实际路径为 node_module 中的依赖,这些代码就是第三方依赖。

假如有如下的模块依赖树,则扫描到的依赖就是 vue 和 axios

模块依赖树是利用模块中的 import 语句(静态 import、动态 import 均可),将各个模块连接起来的。

Vite 文档也同时指出,Vite 默认的依赖发现为启发式,可能并不总是可取

什么时候 Vite 的依赖发现不可靠呢?

当源代码中没有 import 语句,但经过代码编译转换后才有 import 语句,这种情况,Vite 无法依赖扫描。只能在浏览器请求模块,Vite 转换后,在运行时发现新依赖

提出和验证猜想

我们看看项目中的模块依赖树(节选):

router.ts 的部分代码如下:

// 自动导入modules文件夹下所有ts文件
// glob 和 globEager 作用相同,只是转化后,是动态引入还是静态引入的区别
const modules = import.meta.globEager('./modules/**/*.ts');
复制代码

这是一种很常见的用法,所有的 vue-router 配置写到 modules 文件夹下,然后 router.ts 自动引入该文件的所有模块,然后传给 vue-router。

整个项目中,除了 router.ts 中使用 glob 特性进行引入模块外,其他模块均使用静态 import 或动态 import 语句引入模块。因此依赖扫描流程中,唯一可能出现问题的,就是在依赖扫描阶段 glob 没有进行转换

要想验证 Vite3.x 在依赖扫描阶段没有转换 glob,只需要在 Vite2.x 中找到转换代码,而在 Vite3.x 中找不到即可。

经过考证,我从这个 pull request 中得知,Vite3.x 重构了 import.meta.glob 的转换,但却删除对 JS 代码中 glob 的转换,从而导致依赖扫描不全。

知道问题之后,我们只要将 glob 的转换逻辑加上即可

如何修复,这个过程就不细说了,因为也不需要关心了,说多了反而让文章更难理解。

为了进一步了解 Vite 的运行机制,我们研究一下这个问题:

为什么依赖扫描不全,会导致后面的一系列问题(依赖重新构建、页面刷新)

依赖扫描不全后的运行过程

我们需要对照运行日志和模块依赖树,来解析依赖扫描不全后的 Vite 的整个运行过程:

  1. import.meta.glob 没有被转换,Vite 认为 router.ts 下只有 Login.vue,Login.vue 下的依赖被 Vite 发现,但 base.ts 等模块及其嵌套使用的依赖,并没有被扫描到
  2. 第一次依赖预构建完成
  3. 访问页面,执行时,请求 router.ts 页面,router.ts 被 Vite 转换
  4. 浏览器执行 router.ts 代码,动态 import base.ts,在浏览器运行时才知道有 base.ts 模块
  5. 请求 base.ts,Vite 转换 base.ts 并返回
  6. 执行 base.ts 代码,请求静态 import Layout.vue ,Vite 发现新依赖 echarts/charts 等, 重新执行依赖预构建
  7. 第二次依赖预构建完成
  8. 浏览器执行 base.ts 的代码,发现有动态 import dashboard.vue 模块
  9. 请求 dashboard.vue 及其嵌套的模块,发现新依赖 echart/charts,重新执行依赖预构建
  10. 第三次依赖预构建完成

以下是这一过程的图示,从第 3 点开始画的

静态 import 和动态 import 的区别?

静态 import:阻塞代码执行,必须要等 import 的模块加载完成,才会执行当前模块的代码

动态 import:异步加载模块,不阻塞当前模块代码执行。

我们来看下面这个片段。

base.ts 是静态 import Layout.vue 的,因此 base.ts 必须要等它嵌套的依赖加载完成,才会执行。但由于嵌套的 SiderNav 依赖了 lodash/unionlodash/union 又必须等构建完成,才能返回。

因此 base.ts、Layout.vue、SiderNav.vue 三个模块都被阻塞了。

再来看这个片段:

当 base.ts 代码运行时,才发现有动态的 import dashboard.vue,在请求 dashboard.vue 过程中,又发现了新的依赖 echart/charts,又需要重新预构建。

结合这两个片段,我们会发现这两次发现新依赖,并没有办法合成一次构建,即使 Vite 有延迟执行重新构建的能力

因为发现新依赖 lodash/union,base.ts 是被阻塞的,无法执行代码,这就无法知道需要请求 dashboard.vue,也就无法知道有新的依赖 echart/charts

这就是依赖扫描不全导致的严重后果:由于静态 import 阻塞代码执行,导致运行过程中多次发现新依赖,多次重新预构建。

因此这次的修复,其实对性能提升远远大于 25%,原因有以下两点:

  1. 运行过程中还会发现新的依赖,导致重新预构建
  2. 依赖扫描完整后,扫描出非常多的依赖,所有的这些依赖构建时间为 40s;而没修复前,仅仅扫描出少量的依赖,构建时间仅仅不到 10s。两者构建的依赖数量本身就相差较大的。

每次发现新的依赖,必须重新构建吗?

必须重新构建

官方文档提到了, Vite 构建的两个目的:

  1. CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
  2. 性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

因此新的依赖,必须要等构建完成才能返回,期间会造成阻塞

为什么只有最后一次依赖预构建才会刷新页面?

我们来看看三次构建的产物(节选):

  1. 第一次构建,有 echart/core 和 lodash/keys
  2. 第二次构建,新发现了 lodash/union,该依赖跟原有依赖,没有任何公共代码,因此打包的产物也不会相互依赖
  3. 第三次构建,新发现了 echart/charts,它与 echart/core 有公共的依赖,打包产物会多了一份公共的代码,它们都依赖这份公共代码。

第三次构建与第二次构建对比, echart/core 的模块文件已经被改变(原来自己所有代码都在一个模块,现在公共代码被抽离),原先浏览器拉取的 echart.core 代码已经是失效的代码,这时候只能刷新页面,让浏览器重新拉取最新的 echart/core

Vite 实际上会根据打包前后的 file hash,来决定是否需要刷新页面,如果所有依赖的构建前后文件 hash 没有被改变,则不会刷新页面,例如第二次构建,只新增了 lodash/union,其他模块没有被改变。

总结

文章就写到这了,第一次给 Vite 贡献代码,的确有点小激动。虽然是一个小小的 bug,但实际上过程是充满坎坷的,每一个小小的问题都能研究几天,但最后回顾起来,这个过程学到了很多收获还是非常大的。

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

关联阅读

  • 《五千字深度解读 Vite 的依赖扫描》
  • 《快速理解 Vite 的依赖预构建》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/17000.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示)

目录 前言 概述 Vue3组合式api VS Vue2选项式api 基础部分 setup 选项式api的风格 组合式api的风格 区别 响应式数据 ref reactive shallowReactive 与 shallowRef 计算属性和监听 computed 函数 watch 函数 watchEffect 生命周期 响应式数据只读 toRaw 返回代…

呼叫中心中间件(mod_cti基于FreeSWITCH)-背景音(彩话)接口

背景音,就是给通话添加一个背景音,比如办公室的噪音,键盘敲击声,等。彩话,就是通话过程播放一个声音,代替人工说话,这个声音双方可以同时听到,而且播放过程不影响双方通话。 用处 …

「JVS低代码开发平台」关于逻辑引擎的触发讲解

JVS逻辑引擎是代码开发套件中的业务瓶装的核心,用于去实现各种场景下的逻辑功能,可以把他理解为一个程序配置器与程序的执行器。 逻辑引擎是可以被多种配置器调用的触发的,从而实现了各种业务场景中对应功能的实现,那么接下来我们…

RabbitMQ初步到精通-第四章-RabbitMQ工作模式-PUB/SUB

第四章-RabbitMQ工作模式-PUB/SUB 1.模式介绍 1.1 模式 此模式称为发布订阅模式,从此模式开始,我们就不再使用默认的交换机了,开始定义我们自己的交换机。 此发布订阅模式,使用的交换机类型为Fanout。定义好交换机,消…

【MATLAB教程案例42】语音信号的MFCC特征提取matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 本课程学习成果预览: 目录 1.软件版本 2.MFCC理论概述

JavaScript之BOM复习(54th)

1、BOM概述 1、BOM Browser Object Model 浏览器对象模型 2、它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window 3、BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性 4、BOM 缺乏标准,JavaScript 语法的…

用QT实现一个简单的桌面宠物

有时候桌面空空的,或者屏幕空旷了,我们就可以找一点东西来点缀一下,那么桌面宠物是一个不错的选择。 作为一个程序猿,如何实现一个桌面宠物呢? 本文就给大家带来的是如何用qt提供一种思路并写一个简单的桌面宠物。 思…

深入理解Linux网络技术内 幕(八)——设备注册和初始化

文章目录前言设备注册之时设备除名之时分配net_device结构NIC注册和除名的架构设备初始化设备驱动程序初始化设备类型初始化:xxx_setup函数可选的初始化和特殊情况net_device结构的组织查询设备状态队列规则状态注册状态设备的注册和除名切割操作:netdev…

C#编程的构成要素(结合unity做实例分析)

目录 定义变量 变量的名称很重要 将变量作为占位符 疯狂的方法 方法驱动行为 方法也是占位符 类的引入 一直在使用类 日常蓝图 注释是关键 将脚本附加到游戏对象上 脚本成为组件 类与组件通信 本文主要来自<<C#实践入门>>哈里森.费隆 著&#xff0c;仅用…

甘露糖-聚乙二醇-氨基|mannose-PEG-NH2|氨基-PEG-甘露糖

甘露糖-聚乙二醇-氨基|mannose-PEG-NH2|氨基-PEG-甘露糖 氨基&#xff08;Amino&#xff09;由一个氮原子和两个氢原子构成&#xff0c;化学式为-NH2。在有机化学中&#xff0c;氨基是基本碱基&#xff0c;大多数含有氨基的有机物都有一定碱的特性&#xff0c; 中文名称&…

基于数学形态学的路面裂缝图像处理技术-含Matlab代码

⭕⭕ 目 录 ⭕⭕✳️ 一、引言✳️ 二、图像预处理✳️ 三、路面裂缝图像的边缘检测✳️ 3.1 裂缝识别✳️ 3.2 裂缝区域信息获取✳️ 3.3 裂缝特征提取✳️ 四、参考文献✳️ 五、Matlab代码获取✳️ 一、引言 对于路面裂缝而言&#xff0c; 采用图像处理技术对其进行识别与计…

③计算机病毒实验实验报告

班级 计科2101 姓名 彭彭头 学号 时间 2022年5月6日 成绩 实验项目名称 计算机病毒实验二 实验目的 1、了解脚本病毒的感染方式。 2、了解脚本病毒的手工清除方法。 实验内容 通过批处理文件进行计算机病毒和编写&#xff0c;了解脚本病毒的感染方式。 实验环…

Java笔记(十三)

文献种类&#xff1a;专题技术总结文献 开发工具与关键技术&#xff1a; IntelliJ IDEA、Java 语言 作者&#xff1a; 方建恒 年级&#xff1a; 2020 撰写时间&#xff1a; 2022 年 11 月 18 日 Java笔记(十三) 今天我给大家继续分享一下我的Java笔记&#xff0c; 我们继续来…

【Linux】环境基础开发工具使用

Vim Vim 是一个编辑器 只能编辑&#xff0c;只能写代码 直接输入vim &#xff1a; q就是退出 touch新文件&#xff0c;vim 进入 vim是一款多模式的编辑器 命令模式&#xff08;默认打开的模式&#xff09; 按 i 进入编辑模式/插入模式 esc回到命令模式 冒号进入底行…

【前沿技术RPA】 一文了解UiPath的代码审查工具Workflow Analyzer

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

[附源码]java毕业设计企业员工管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Azdio-PEG-Maleimide,N3-PEG-MAL,叠氮-PEG-马来酰亚胺化学试剂供应

1、名称 英文&#xff1a;Azdio-PEG-Maleimide&#xff0c;N3-PEG-MAL 中文&#xff1a;叠氮-聚乙二醇-马来酰亚胺 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Azide PEG Maleimide PEG 4、分子量&#xff1a;可定制&#xff0c;N3-PEG 20k -MAL、N3-PEG 10k -MAL…

EFK部署centos7.9(四)Filebeat 部署

下载安装包 wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.5.4-linux-x86_64.tar.gz tar xzvf filebeat-6.5.4-linux-x86_64.tar.gz -C /usr/local/ 解压安装包 cd /usr/local/ mv filebeat-6.5.4-linux-x86_64 filebeat cd filebeat/ mv filebe…

Springboot导出Excel,支持大数据量

1、添加maven依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version> </dependency> 2、ExcelUtil工具类 import org.apache.poi.ss.usermodel.Cell; import org.…

【附源码】Python计算机毕业设计天气预报APP

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;我…