Vite: 关于预构建的毫秒级响应

news2024/11/13 12:02:43

概述

  • 在我们的项目代码中,我们所说的模块代码其实分为两部分
    • 一部分是源代码,也就是业务代码
    • 另一部分是第三方依赖的代码,即 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:
      react
      react-dom
      react/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 插件 修改模块加载的内容

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

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

相关文章

主机游戏也可以上云桌面玩了?

最近steam夏季促销活动也快到了&#xff0c;对于很多钟情于主机游戏的小伙伴们&#xff0c;是不是也在摩拳擦掌了&#xff1f; 但有时候现实想愉快地玩到自己想玩的游戏实在是太难了&#xff01; 当你一直关注的新游戏终于上线Steam时&#xff0c;你的钱包是这样的… 而游戏的…

elementUI相关知识及搭建使用过程

​​​​​​ 目录 ​​​​​​ 一.elementUI相关的知识 1.什么是elementUI 2.如何在创建的项目中使用elementUI的组件(1)安装 ​ (2)在项目的main.js中引入elementUI (3)使用elementui里的组件 一.elementUI相关的知识 1.什么是elementUI Element&#xff0c;一套为开…

【管理咨询宝藏136】RB大型卡车集团供应链体系优化设计方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏136】RB大型卡车集团供应链体系优化设计方案 【格式】PDF版本 【关键词】罗兰贝格、供应链管理、运营提升 【核心观点】 - 针对前两个模块&…

Charles配置与API数据抓取

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…

基于STM32的智能健康监测手表

目录 引言环境准备智能健康监测手表系统基础代码实现&#xff1a;实现智能健康监测手表系统 4.1 数据采集模块4.2 数据处理与分析4.3 通信模块实现4.4 用户界面与数据可视化应用场景&#xff1a;健康监测与管理问题解决方案与优化收尾与总结 1. 引言 智能健康监测手表通过使…

美团SaaS技术部测开,复捞我开摆

美团SaaS技术部测开&#xff0c;复捞我开摆 4.3 80min 项目实习你主要承担的部分你做的项目技术实现是什么样的&#xff0c;前后端是怎么做出来的自动登录功能的架构软件测试全流程你有了解吗搭建测试环境是否可有可无哪些举措是你牵头做的发现了什么问题在代码核验阶段你如何…

C++STL梳理

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

【消息队列】Kafka学习笔记

概述 定义 传统定义: 一个分布式的, 基于发布订阅模式的消息队列, 主要应用于大数据实时处理领域新定义: 开源的分布式事件流平台, 被用于数据管道/流分析/数据集成 消息队列的使用场景 传统消息队列的主要应用场景包括: 削峰: 解耦: 异步: 两种模式 点对点模式 发布/订…

Java基础:IO流

目录 一、定义 1.引言 2.分类 &#xff08;1&#xff09;按照流的方向分 &#xff08;2&#xff09;按操作文件的类型分 3.体系结构 二、字节流&#xff08;以操作本地文件为例&#xff09; 1. FileOutputStream 类 &#xff08;1&#xff09;定义 &#xff08;2&am…

【Linux】Linux编译器(gcc,g++)与动静态链接库

对于一个 C 程序&#xff0c;从源文件到形成可执行程序一共要进行四步&#xff1a;预处理、编译、汇编、链接 。 接下来&#xff0c;我们用 gcc 分别演示这四个过程。 一、预处理(进行宏替换) 预处理中&#xff0c;需要完成头文件的展开、宏替换、去注释、条件编译等工作。 …

说一说三大运营商的流量类型,看完就知道该怎么选运营商了!

说一说三大运营商的流量类型&#xff0c;看完就知道该怎么选运营商了&#xff1f;目前三大运营商的流量类型大致分为通用流量和定向流量&#xff0c;比如&#xff1a; 中国电信&#xff1a;通用流量定向流量 电信推出的套餐通常由通用流量定向流量所组成&#xff0c;通用流量…

《数字图像处理》实验报告一

一、实验任务与要求 1、用 matlab 编写空间域点处理操作处理给定的几幅图像&#xff0c;要求&#xff1a; 使用 imread 读取当前工作目录下的图像设计点处理操作并用代码实现处理用 imnshow 显示处理后的图像用 imwrite 保存处理后的图像 2、提交内容&#xff1a;m文件 实验…

假冒国企现形记:股权变更视角下的甄别分析

启信慧眼-启信宝企业版 假冒国企公告2024-06-07&#xff0c;中粮集团有限公司官网发布《关于冒名中粮企业名单公告》。公告显示&#xff0c;”有不法分子通过伪造相关材料等方式&#xff0c;以我集团子公司名义开展业务&#xff0c;进行虚假宣传。经核实&#xff0c;上述公司假…

【SpringCloud】API网关(Spring Cloud Gateway)

本文基于上一篇http://t.csdnimg.cn/q3YrK 使用抽取的方案使用feign的基础上使用Spring Cloud Gateway。 API网关 API网关&#xff08;简称网关&#xff09;也是一个服务&#xff0c;通常是后端服务的唯一入口。它就像是整个微服务架构的门面&#xff0c;所有的外部客户端访问…

Python学习笔记13:进阶篇(二),类的继承与组合

类的继承 我们在编写一系列的类的时候&#xff0c;会发现这些类很相似&#xff0c;但是又有各自的特点和行为。在编写这些类的时候&#xff0c;我们可以把相同的部分抽象成一个基类&#xff0c;然后根据其他不同的特点和行为&#xff0c;抽象出子类&#xff0c;继承这个基类。…

python实训day4

1、查看数据库的版本 2、查看当前用户 3、查看当前数据库 4、计算表达式的结果; 任何一个数据库,无论大小,都首先是一个超级计算器 5、查看当前MySQL环境中所有的数据库; 系统数据库(只能看)和自定义数据库(任何操作) 6、先建数据库 gaoming 7、如果表已经存在,则创建不能成功 …

刷题之小欧的平均数(卡码网)

小欧的平均数 这道题不看解析的话完全没有思路&#xff0c;连题目都没读明白&#xff0c;甚至看了评论答出来了还是不知道为什么&#xff0c;有知道的朋友可以教教我 #include<iostream> using namespace std;int main() {int x,y,z;cin>>x>>y>>z;//…

oracle12c到19c adg搭建(六)切换后12c备库服务器安装19c软件在19c主库升级数据字典后尝试同步

一、安装19c软件 参考文章oracle12c到19c adg搭建&#xff08;三&#xff09;oracle19c数据库软件安装 二、原主库尝试通过19c软件启动数据库 2.1复制12c的相关参数文件和密码文件到19c目录 注意:密码文件需要从已切换主库19c传过来 [oracleo12u19p ~]$ cd /u01/app/oracle…

亲测有效!性能压测异常竟能自动化分析!

性能压测是一种评估系统运行效率和稳定性的方法&#xff0c;通过模拟真实的使用场景和负载条件&#xff0c;对系统进行压力测试和负载测试&#xff0c;并对测试结果进行分析&#xff0c;以评估系统的性能&#xff0c;其中性能压测结果分析是性能压测的重要环节。以往的性能压测…

ONLYOFFICE 桌面编辑器 8.1 发布:全新功能齐备的 PDF 编辑器、丰富的幻灯片版式

前言 在当前数字化迅速发展的时代&#xff0c;办公软件套件作为企业日常工作的核心工具&#xff0c;其功能的全面性和使用的便捷性直接影响着工作效率和团队协作的效果。在众多办公软件中&#xff0c;ONLYOFFICE凭借其卓越的性能和丰富的功能&#xff0c;备受用户青睐。近日&a…