Vite: 关于预构建的毫秒级响应
概述
- 在我们的项目代码中,我们所说的模块代码其实分为两部分
- 一部分是源代码,也就是业务代码
- 另一部分是第三方依赖的代码,即 node_modules 中的代码
- Vite 是一个提倡 no-bundle 的构建工具,相比于传统的 Webpack
- 能做到开发时的模块按需编译,而不用先打包完再加载
- 所谓的 no-bundle 只是对于源代码而言,对于第三方依赖而言,Vite 还是选择 bundle(打包)
- 并且使用速度极快的打包器 Esbuild 来完成这一过程,达到秒级的依赖编译速度
为何需要预构建
1 ) 第三方库
- 为什么在开发阶段我们要对第三方依赖进行预构建呢?
- 首先 Vite 是基于浏览器原生 ES 模块规范实现的 Dev Server
- 不论是应用代码,还是第三方依赖的代码,理应符合 ESM 规范才能够正常运行
- 但是,我们没有办法控制第三方的打包规范
- 就目前来看,有相当多的第三方库仍然没有 ES 版本的产物,比如 react
// react 入口文件 if (process.env.NODE_ENV === "production") {module.exports = require("./cjs/react.production.min.js"); } else {module.exports = require("./cjs/react.development.js"); }
- 可见这是 CommonJS 规范
- 这种 CommonJS 格式的代码在 Vite 当中无法直接运行
- 我们需要将它转换成 ESM 格式的产物
2 )函数的依赖
- 举个例子,知名的 loadsh-es 库,我们在调用其中的一个函数的时候
- 这个函数可能需要用到其他的函数,也就是在这个函数的内部会 import 其他的文件
- 在其他文件中,同样还会import更多,这样就会造成一个问题
- 它在加载时会发出特别多的请求,导致页面加载的前几秒几都乎处于卡顿状态
- 因此在这种 依赖层级深 、 涉及模块数量多的情况下,会触发成百上千个网络请求
- 巨大的请求量加上 Chrome 对同一个域名下只能同时支持 6 个 HTTP 并发请求的限制
- 导致页面加载十分缓慢,与 Vite 主导性能优势的初衷背道而驰
- 不过,在进行依赖的预构建之后, lodash-es 这个库的代码被打包成了一个文件
- 这样请求的数量会骤然减少,页面加载也快了许多
3 )综上
- 依赖预构建主要做了两件事情:
- 一是将其他格式(如 UMD 和 CommonJS)的产物转换为 ESM 格式
- 使其在浏览器通过
<script type="module"><script>
的方式正常加载 - 二是打包第三方库的代码,将各个第三方库分散的文件合并到一起
- 减少 HTTP 请求数量,避免页面加载性能劣化
- 这两件事情全部由性能优异的 Esbuild (基于 Golang 开发)完成
- 而不是传统的Webpack/Rollup,所以也不会有明显的打包性能问题
- 反而是 Vite 项目启动飞快(秒级启动)的一个核心原因
- 注意
- Vite 1.x 使用了 Rollup 来进行依赖预构建
- 在 2.x 版本将 Rollup 换成了Esbuild,编译速度提升了近 100 倍!
开启预构建
- 在 Vite 中有两种开启预构建的方式,分别是
自动开启
和手动开启
1 ) 自动开启
- 首先是 自动开启, 当我们在第一次启动项目的时候 $
pnpm run dev
, 可以在命令行窗口看见如下的信息Pre-bundling dependencies:reactreact-domreact/jsx-dev-runtime (this will be run only when your dependencies or config have changed)
- 同时,在项目启动成功后, 你可以在根目录下的 node_modules 中发现 .vite 目录
- 这就是预构建产物文件存放的目录,内容如下

- 在浏览器访问页面后,打开 Dev Tools 中的网络调试面板,你可以发现第三方包的引入路径已经被重写

- 并且对于依赖的请求结果,Vite 的 Dev Server 会设置强缓存

- 缓存过期时间被设置为一年,表示缓存过期前浏览器对 react 预构建产物的请求不会再经
过 Vite Dev Server,直接用缓存结果 - 当然,除了 HTTP 缓存,Vite 还设置了本地文件系统的缓存,所有的预构建产物默认缓
存在 node_modules/.vite 目录中 - 如果以下 3 个地方都没有改动,Vite 将一直使用缓存文件:
- package.json 的 dependencies 字段
- 各种包管理器的 lock 文件
- optimizeDeps 配置内容
2 )手动开启
- 上面提到了预构建中本地文件系统的产物缓存机制,而少数场景下不希望用本地的缓存文件
- 比如需要调试某个包的预构建结果,我推荐使用下面任意一种方法清除缓存,还有手动开启预构建:
- 删除 node_modules/.vite 目录。
- 在 Vite 配置文件中,将
server.force
设为 true - 命令行执行
npx vite --force
或者npx vite optimize
- Vite 项目的启动可以分为两步
- 第一步是依赖预构建
- 第二步才是 Dev Server 的启动, $
npx vite optimize
相比于其它的方案,仅仅完成第一步的功能
预构建的自定义配置
- 通过 Vite 提供的配置项来定制预构建的过程
- Vite 将预构建相关的配置项都集中在 optimizeDeps 属性上
- 我们来一一拆解这些子配置项背后的含义和应用场景
1 )入口文件——entries
- 第一个是参数是
optimizeDeps.entries
,通过这个参数你可以自定义预构建的入口文
件 - 实际上,在项目第一次启动时,Vite 会默认抓取项目中所有的 HTML 文件(如当前脚手
架项目中的 index.html ) - 将 HTML 文件作为应用入口,然后根据入口文件扫描出项目中用到的第三方依赖, 最后对这些依赖逐个进行编译
- 那么,当默认扫描 HTML 文件的行为无法满足需求的时候,比如项目入口为 vue 格式文件时,你可以通过 entries 参数来配置:
// vite.config.ts {optimizeDeps: {// 为一个字符串数组entries: ["./src/main.vue"];} }
- entries 配置也支持 glob 语法,非常灵活,如:
// 将所有的 .vue 文件作为扫描入口 entries: ["**/*.vue"];
- 不光是 .vue 文件,Vite 同时还支持各种格式的入口
- 包括: html 、 svelte 、 astro 、js 、 jsx 、 ts 和 tsx
- 可以看到,只要可能存在 import 语句的地方,Vite 都可以解析
- 并通过内置的扫描机制搜集到项目中用到的依赖,通用性很强
2 )添加一些依赖——include
- 除了 entries , include 也是一个很常用的配置
- 它决定了可以强制预构建的依赖项,使用方式很简单:
// vite.config.ts optimizeDeps: {// 配置为一个字符串数组,将 `lodash-es` 和 `vue`两个包强制进行预构建include: ["lodash-es", "vue"]; }
- 它在使用上并不难,真正难的地方在于,如何找到合适它的使用场景
- 前文中我们提到,Vite 会根据应用入口( entries )自动搜集依赖,然后进行预构建
- 这是不是说明 Vite 可以百分百准确地搜集到所有的依赖呢?
- 事实上并不是,某些情况下 Vite 默认的扫描行为并不完全可靠
- 这就需要联合配置 include 来达到完美的预构建效果了
- 接下来,我们好好梳理一下到底有哪些需要配置 include 的场景
2.1 场景一: 动态 import
- 在某些动态 import 的场景下,由于 Vite 天然按需加载的特性
- 经常会导致某些依赖只能在运行时被识别出来
// src/locales/zh_CN.js import objectAssign from "object-assign"; console.log(objectAssign); // main.tsx const importModule = (m) => import(`./locales/${m}.ts`); importModule("zh_CN");
- 在这个例子中,动态 import 的路径只有运行时才能确定,无法在预构建阶段被扫描出来
- 因此,我们在访问项目时控制台会出现下面的日志信息
[vite] new dependencies found: object-assign, updating... [vite] ✨ dependencies updated, reloading page...
- 这段 log 的意思是: Vite 运行时发现了新的依赖,随之重新进行依赖预构建,并刷新页
面,这个过程也叫二次预构建。 - 在一些比较复杂的项目中,这个过程会执行很多次,如下面的日志信息所示:
[vite] new dependencies found: @material-ui/icons/Dehaze, @material-ui/core/Box, @material-ui [vite] ✨ dependencies updated, reloading page... [vite] new dependencies found: @material-ui/core/Dialog, @material-ui/core/DialogActions, upd [vite] ✨ dependencies updated, reloading page... [vite] new dependencies found: @material-ui/core/Accordion, @material-ui/core/AccordionSummar [vite] ✨ dependencies updated, reloading page...
- 然而,二次预构建的成本也比较大,不仅需要把预构建的流程重新运行一遍,还得重
新刷新页面,并且需要重新请求所有的模块 - 尤其是在大型项目中,这个过程会严重拖慢应用的加载速度!
- 因此,我们要尽力避免运行时的二次预构建,具体怎么做呢?
- 你可以通过 include 参数提前声明需要按需加载的依赖:
// vite.config.ts {optimizeDeps: {include: [// 按需加载的依赖都可以声明到这个数组里"object-assign",];} }
2.2 场景二: 某些包被手动 exclude
- exclude 是 optimizeDeps 中的另一个配置项,与 include 相对,用于将某些依赖从预构
建的过程中排除 - 不过这个配置并不常用,也不推荐使用,如果真遇到了要在预构建中排除某个包的情况
- 需要注意,它所依赖的包是否具有 ESM 格式,如下面这个例子
// vite.config.ts {optimizeDeps: {exclude: ["@loadable/component"];} }
- 这时候,可以看到浏览器控制台会出现报错
- 因为我们刚刚手动 exclude 的包 @loadable/component 本身具有 ESM 格式的产物
- 但它的某个依赖 hoist-non-react-statics 的产物并没有提供 ESM 格式,导致运行时加载失败
- 这个时候 include 配置就派上用场了,我们可以强制对
hoist-non-react-statics
这个间接依赖进行预构建:// vite.config.ts {optimizeDeps: {include: [// 间接依赖的声明语法,通过`>`分开, 如`a > b`表示 a 中依赖的 b"@loadable/component > hoist-non-react-statics",];} }
- 在 include 参数中,我们将所有不具备 ESM 格式产物包都声明一遍,这样再次启动项目
就没有问题了
2.3 场景三: 自定义 Esbuild 行为
- Vite 提供了 esbuildOptions 参数来让我们自定义 Esbuild 本身的配置
- 常用的场景是加入一些 Esbuild 插件:
// vite.config.ts {optimizeDeps: {esbuildOptions: {plugins: [// 加入 Esbuild 插件];}} }
- 这个配置主要是处理一些特殊情况
2.4 场景四:第三方包出现问题
- 由于我们无法保证第三方包的代码质量,在某些情况下我们会遇到莫名的第三方库报错
- 我举一个常见的案例—— react-virtualized 库
- 这个库被许多组件库用到,但它的ESM 格式产物有明显的问题
- 在 Vite 进行预构建的时候会直接抛出错误
- 原因是这个库的 ES 产物莫名其妙多出了一行无用的代码
- 其实我们并不需要这行代码,但它却导致 Esbuild 预构建的时候直接报错退出了
- 那这一类的问题如何解决呢?
A )改第三方库代码
- 直接修改第三方库的代码,不过这会带来团队协作的问题
- 你的改动需要同步到团队所有成员,比较麻烦
- 好在,我们可以使用 patch-package 这个库来解决这类问题
- 一方面,它能记录第三方库代码的改动,另一方面也能将改动同步到团队每个成员
- patch-package 官方只支持 npm 和 yarn,而不支持 pnpm
- 不过社区中已经提供了支持 pnpm 的版本,这里我们来安装一下相应的包:
- $
pnpm i @milahu/patch-package -D
- 注意: 要改动的包在 package.json 中必须声明确定的版本
- 不能有 ~ 或者 ^ 的前缀
- $
- 接着,我们进入第三方库的代码中进行修改,先删掉无用的 import 语句,再在命令行输
入: $npx patch-package react-virtualized
- 现在根目录会多出 patches 目录记录第三方包内容的更改
- 随后我们在 package.json 的 scripts 中增加如下内容:
{"scripts": {// 省略其它 script"postinstall": "patch-package"} }
- 这样一来,每次安装依赖的时候都会通过 postinstall 脚本自动应用 patches 的修改,
解决了团队协作的问题
B )加入 Esbuild 插件
- 第二种方式是通过 Esbuild 插件修改指定模块的内容
- 这里展示一下新增的配置内容:
// vite.config.ts const esbuildPatchPlugin = {name: "react-virtualized-patch",setup(build) {build.onLoad({filter: /react-virtualized\/dist\/es\/WindowScroller\/utils\/onScroll.js$/,},async (args) => {const text = await fs.promises.readFile(args.path, "utf8");return {contents: text.replace('import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";',""),};});}, };// 插件加入 Vite 预构建配置 {optimizeDeps: {esbuildOptions: {plugins: [esbuildPatchPlugin];}} }
总结
- 预构建的相关配置—— entries 、 include 、 exclude 和 esbuldOptions ,
- 还有第三方包出现了问题的两个解决思路:
- 通过 patch-package 修改库代码
- 编写 Esbuild 插件 修改模块加载的内容
相关文章:

Vite: 关于预构建的毫秒级响应
概述 在我们的项目代码中,我们所说的模块代码其实分为两部分 一部分是源代码,也就是业务代码另一部分是第三方依赖的代码,即 node_modules 中的代码 Vite 是一个提倡 no-bundle 的构建工具,相比于传统的 Webpack能做到开发时的模…...

Docker 中 MySQL 迁移策略(单节点)
目录 一、 简介二、操作流程2.1 进入mysql容器2.2 导出 MySQL 数据2.3. 将导出的文件复制到宿主机2.4 创建 Docker Compose 配置2.5 启动新的 Docker 容器2.6 导入数据到新的容器2.7 验证数据2.8 删除旧的容器(删除操作需慎重) 三、推荐配置四、写在后面…...

猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests
猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests 🐯 摘要 📄 大家好,我是猫头虎,一名专注于人工智能领域的博主。在AI开发中,我们经常会遇到各种各样的错误,其中API Rate Limits…...

开发一个python工具,pdf转图片,并且截成单个图片,然后修整没用的白边及循环遍历文件夹全量压缩图片
今天推荐一键款本人开发的pdf转单张图片并截取没有用的白边工具 一、开发背景: 业务需要将一个pdf文件展示在前端显示,但是基于各种原因,放弃了h5使用插件展示 原因有多个,文件资源太大加载太慢、pdf展示兼容性问题、pdf展示效果…...

【数据结构与算法 经典例题】使用栈实现队列(图文详解)
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《数据结构与算法 经典例题》C语言 期待您的关注 目录 一、问题描述 二、前置知识 三、解题思路 原理: 图解&…...

不知大家信不信,竟有这么巧的事,我领导的老婆,竟然是我老婆的下属,我在想要不要利用下这层关系,改善下领导对我的态度,领导怕老婆
职场如战场,每个人都身不由己。每天上班,除了要面对堆积如山的工作,还要小心应对来自领导的“狂风暴雨”。最近,我无意间发现领导一个秘密,这个秘密让我对职场关系和人性都产生了新的思考。 故事要从那天晚上说起。我…...

使用pkg -r 命令选项向jail虚拟子系统里安装软件@FreeBSD
刷FreeBSD 论坛的时候,看到这样一招:使用pkg -r选项,往jail等虚拟机子系统里安装软件。jails - How to install a pkg offline into a jail? | The FreeBSD Forums rootfbhost:~ # pkg pkg: not enough arguments Usage: pkg [-v] [-d] [-l…...

Go语言开发框架GoFly已集成数据可视化大屏开发功能,让开发者只专注业务开发,本文指导大家如何使用
前言 框架提供数据大屏开发基础,是考虑当前市场软件应用有一大部分是需要把业务数据做出大屏,很多政府项目对大屏需求特别高,还有生产企业项目也对大屏有需求,没有提供基础规范的后台框架,在开发大屏需要很多时间去基…...

PR模板 | RGB特效视频标题模板Titles | MOGRT
RGB特效视频标题模板mogrt免费下载 4K分辨率(38402160) 支持任何语言 友好的界面 输入和输出动画 快速渲染 视频教程 免费下载:https://prmuban.com/39055.html 更多pr模板视频素材下载地址:https://prmuban.com...

python替换文件内容
# 打开文件with open(name, r) as file:content file.read()# 替换内容old_string binarynew_string cc_library_sharedcontent content.replace(old_string, new_string)# 写回文件with open(name, w) as file:file.write(content)...

SD-WAN是什么?它有哪些应用领域?
随着企业业务的不断扩展和数字化转型的加速,传统网络架构已无法满足企业对高效、灵活和安全网络连接的需求。在此背景下,SD-WAN(软件定义广域网)应运而生,为企业带来了全新的网络连接体验。本文将详细介绍SD-WAN网络及…...

PHP-CGI的漏洞(CVE-2024-4577)
通过前两篇文章的铺垫,现在我们可以了解 CVE-2024-4577这个漏洞的原理 漏洞原理 CVE-2024-4577是CVE-2012-1823这个老漏洞的绕过,php cgi的老漏洞至今已经12年,具体可以参考我的另一个文档 简单来说,就是使用cgi模式运行的PHP&…...

人工智能前沿讲座——AIGC
目录 前情提要 一、什么是AIGC AIGC与传统的AI有何区别? 二、发展历程 GAN 生成对抗网络 大模型与Transformer Transformer\BERT\GPT 扩散模型和稳定扩散模型 三、AIGC的发展应用 新质生产力 前情提要 小学期某一门课的笔记,老师名字隐去&…...

CCF 第33次CCF计算机软件能力认证第二题
相似度计算 刷新 时间限制: 1.0 秒 空间限制: 512 MiB 下载题目目录(样例文件) 题目背景 两个集合的 Jaccard 相似度定义为:𝑆𝑖𝑚(𝐴,𝐵)∣…...

python 学习积累
持续更新中 感受python的强大之case列举: 1. 生成的map list要经过json格式化写入文件,请用python实现这一需求 import json map{"name": "张三", "age": 18, "address": "北京"} list[] for i in …...

ARM day1总结
思维导图...

套路化编程:C# ListView 保存、恢复列宽度
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 目录 技术基础 保存列头 删…...

python单元测试
文章目录 单元测试定义断言函数Test FixturesMockpatch装饰器模拟(首选)上下文管理器模拟手动模拟 测试实例 测试覆盖率pytest框架起步安装使用常用参数跳过测试pytest.fixtureconftest.py参数化测试 数据库查询的mock覆盖率 单元测试 定义 单元测试是…...

华为---静态路由-浮动静态路由及负载均衡(二)
7.2 浮动静态路由及负载均衡 7.2.1 原理概述 浮动静态路由(Floating Static Route)是一种特殊的静态路由,通过配置去往相同的目的网段,但优先级不同的静态路由,以保证在网络中优先级较高的路由,即主路由失效的情况下,…...

Maven deploy上传远程私服失败
Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy (default-deploy) on project 你的项目: Cannot deploy artifacts when Maven is in offline mode 解决方案: 1.IDEA把这个钩子去掉 2. settings.xml里把 <offline>标…...

通天星CMSV6车载定位监控平台 point_manage/merge SQL注入致RCE漏洞复现
0x01 产品简介 通天星CMSV6车载定位监控平台拥有以位置服务、无线3G/4G视频传输、云存储服务为核心的研发团队,专注于为定位、无线视频终端产品提供平台服务,通天星CMSV6产品覆盖车载录像机、单兵录像机、网络监控摄像机、行驶记录仪等产品的视频综合平台。 0x02 漏洞概述 …...

图像识别技术在人脸识别领域的新突破
图像识别技术在人脸识别领域的新突破主要体现在多个方面,这些突破不仅提高了人脸识别的准确性和效率,还拓展了其应用领域。以下是对这些新突破的详细归纳: 深度学习技术的应用: 深度学习技术,特别是卷积神经网络&…...

iview 组件里面的(任何一个月)整月日期全部选中_iview时间轴选中有历史记录日期
iview 组件里面的整月日期全部选中: ①:第一种是当前月的日期全部选中: 先上效果图:当前月分 获取到的值: 当前月的方法: // getDateStr() {// var curDate new Date();// var curMonth curDate.ge…...

Charles配置与API数据抓取
2024软件测试面试刷题,这个小程序(永久刷题),靠它快速找到工作了!(刷题APP的天花板)-CSDN博客跳槽涨薪的朋友们有福了,今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…...

[FreeRTOS 内部实现] 信号量
文章目录 基础知识创建信号量获取信号量释放信号量信号量 内部实现框图 基础知识 [FreeRTOS 基础知识] 信号量 概念 创建信号量 #define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) #define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U ) #define xSe…...

Vue57-组件的自定义事件_解绑
给谁绑的自定义事件,就找谁去触发;给谁绑的自定义事件,就找谁去解绑; 一、解绑自定义事件 1-1、解绑一个自定义事件 到student.vue组件中去解绑。 1-2、解绑多个自定义事件 使用数组来解绑多个。 1-3、解绑所有的自定义事件 二、…...

Java启动jar设置内存分配详解
在微服务架构越来越盛行的情况下,我们通常一个系统都会拆成很多个小的服务,但是最终部署的时候又因为没有那么多服务器只能把多个服务部署在同一台服务器上,这个时候问题就来了,服务器内存不够,这个时候我们就需要对每…...

Feign Client超时时间设置不生效问题
在使用Feign Client时,可以通过两种方式来设置超时时间: 针对整个Feign Client设置超时时间 可以在Feign Client的配置类中通过修改Request.Options对象来设置超时时间。Request.Options对象有两个属性,connectTimeoutMillis用于设置连接超…...

Haproxy部署Web群集
概论 HAProxy是可提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,是免费、快速并且可靠的一种解决方案。HAProxy非常适用于并发大(并发达1w以上)web站点,这些站点通常又需要会话保持或七层处理。HAProxy的运行模式使得它可以…...

C++STL梳理
CSTL标准手册: https://cplusplus.com/reference/stl/ https://cplusplus.com/reference/vector/vector/at/ 1、STL基础 1.1、STL基本组成(6大组件13个头文件) 通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成&…...