当前位置: 首页 > news >正文

Vite性能优化指南 ✅

目录

1️⃣ 树摇
2️⃣ 分包
3️⃣ 多入口打包
4️⃣ 其他优化手段


树摇

“树摇”这玩意并非某个构建工具的专利,应该说基于ESM模块化开发的项目都有能力进行树摇。详细的分析可见煮啵的另一篇文章中的最后一小节,最核心的原因是CJS的require本质上是一个运行时的函数。此处只介绍树摇带来的具体效果:

// utils/index.js
export function util1() {console.log('util1')
}
export function util2() {console.log('util2')
}// main.js 入口文件
import { util1 } from './utils/index.js
console.log(util1)

这样的代码在打包后会摒弃未被使用的util2,只保留被使用的util1。但如果用CJS模块化进行改写:

// utils/index.js
exports.util1 = () => console.log('util1')
exports.util2 = () => console.log('util2')// main.js 入口文件
const { util1 } = require('./utils/index.js')

这种写法在打包后,即使util2未被使用,但也会带打包产物中被保留。


分包

在PDD的三面中被狠狠拷打🤯
“分包”即所谓的“代码分割”。常用的实现分包的方法有两种:通过修改Vite配置文件进行分包,或者通过动态导入语法import()进行代码分割。

通过修改Vite配置文件

设想一下:在我们的项目中引入了lodash,在进行分包前,lodash相关的代码会和业务代码一起被打包到同一个chunk中,且chunk的命名是通过哈希的方法来生成的,只要业务代码发生改动,那么chunk的命名几乎就会改变。
问题是,每一次更新我们修改的只是业务代码,lodash这些第三方包的代码我们肯定是不会改动的,但是每一次更新(甚至是每一次刷新)浏览器都得重新请求一次lodash的代码。因为lodash所在的chunk的名称会随版本的更新而变化,对这个chunk的请求浏览器走不了缓存。
所以我们的需求很简单:有没有办法让lodash被打包到一个单独的chunk中,后续浏览器只在第一次加载的时候请求这个chunk即可,后续都读缓存,以提高响应速度?有的兄弟,有的🤓🤓,以下介绍最基本的分包方法(详细的配置可见rollup的文档):

// vite.config.js
import { defineConfig } from "vite";
export default defineConfig({build: {minify: false,  // 取消默认的压缩行为,方便观察打包产物rollupOptions: {output: {manualChunks: (id, { getModuleInfo }) => {// getModuleInfo可以查看当前模块的信息if (id.includes('node_modules/lodash')) return "vendor/lodash"}}}},
})

通过import()动态导入

import()动态导入也是实现代码分割的方法,这种优化本质上是借助Promise实现的,详细的分析见煮啵的另一篇博文的最后一章。import动态导入常用于实现路由懒加载(组件按需引入)。以React为例,见以下Demo:

// LazyComponent.tsx
export default function LazyComponent() {return <div>LazyComponent</div>;
}// App.tsx
import { lazy } from "react";
// 动态(按需)引用懒加载组件
const LazyComponent = lazy(() => import("./LazyComponent/index"));
const App = () => {return (<div><div>App</div><LazyComponent /></div>);
};
export default App;

之后不管是Chrome控制台还是本地打包产物,都可以看见LazyComponent组件相关的代码被抽离为一个单独的chunk。


多入口打包

多入口打包允许你将一个项目拆分为多个独立的入口文件(如前台index.html后台admin.html),每个入口生成单独的依赖图和资源包。这种优化方法更适合多页面应用MPA,不适合常见的SPA应用。~~煮啵用的是在是不多。~~假设现在前端股工程的目录如下:
App/
|—src
|——scriptA.js # 脚本A
|——scriptB.js # 脚本B
|——utils.js # 工具函数
|—indexA.html # 入口A
|—indexB.html # 入口B
|—vite.config.js
代码如下:

// utils.js
export function sayHello() {console.log('Hello')
}
export function sayGoodbye() {console.log('Goodbye')
}// scriptA.js
import { sayHello } from "./utils";
function scriptA() {console.log("scriptA is running");sayHello();
}
scriptA();// scriptB.js
import { sayHello, sayGoodbye } from "./utils";
function scriptB() {console.log("scriptB is running");sayHello();sayGoodbye();
}
scriptB();// vite.config.js
export default defineConfig({build: {minify: false,  // 取消默认的压缩行为,方便观察打包后的产物rollupOptions: {input: {scriptA: path.resolve(__dirname, 'src/indexA.html'),scriptB: path.resolve(__dirname, 'src/indexB.html'),},}},
})

此时Vite会以scriptA.js和scriptB.js这两个文件为入口,打包的产物有三个脚本文件,分别为:

// scriptA-HashCode.js
import { s as sayHello } from "./index-BVlMXCZJ.js";
function scriptA() {console.log("scriptA is running");sayHello();
}
scriptA();// scriptB-HashCode.js
import { s as sayHello, a as sayGoodbye } from "./index-BVlMXCZJ.js";
function scriptB() {console.log("scriptB is running");sayHello();sayGoodbye();
}
scriptB();// index-HashCode.js
(function polyfill() {// ...额外的与Vite相关的代码。
})()
function sayHello() {console.log("Hello");
}
function sayGoodbye() {console.log("Goodbye");
}
export {sayGoodbye as a,sayHello as s
};

此处Vite已进行了默认的优化。默认情况下被引用了至少两次的文件,会被打成一个独立的包。因为sayHello被多次复用,在构建中次函数所属的文件会被单独抽离出来。如果sayHello只在一处被调用,那么打包后它会在调用处的chunk中被声明。


其他优化手段

图片转Base64

关于Base64的分析见煮啵另一篇博文的最后一章。Vite默认将小于4KB的图片转为Base64格式,也可以自己手动调整:

// vite.config.js
export default {build: {assetsInlineLimit: 4096, // 调整阈值},
};

开启资源压缩

Vite默认 (没有手动配过,一直用的是默认) 使用 Terser 压缩 JS,ESBuild 压缩 CSS:

// vite.config.js
export default {build: {minify: 'terser', // 或 'esbuild'(更快但压缩率略低)terserOptions: {  // 自定义 Terser 配置compress: { drop_console: true },},},
};

暂时只想到这么多。后续有新的再来补充🧊

http://www.xdnf.cn/news/220375.html

相关文章:

  • 强化学习(二)马尔科夫决策过程(MDP)
  • java AsyncTool
  • ACTF2025 - WEB Excellent-Site
  • 第十章:CrewAI - 面向流程的多 Agent 结构化协作
  • Andorid车机UI适配,AndroidUI图px的单位,如何适配1920x720,PPI100的屏幕设备
  • 【GESP】C++三级练习 luogu-B2117 整理药名
  • Rockchip Android平台打开GKI无法开机问题
  • 应用服务器-IIS
  • 推荐系统中 Label 回收机制之【时间窗口设计】
  • 基于Lucene的多场景检索系统开发指南
  • [按键安卓ios脚本辅助插件开发]数组排序函数例子
  • 明远智睿SSD2351开发板:开启嵌入式开发新篇程
  • C#实现对达索(Dassault)SolidWorks中3D图纸转化为手机可直接查看预览图纸格式
  • 高级项目管理
  • 巧记英语四级单词 Unit6-下【晓艳老师版】
  • C++程序退出时的对象析构陷阱:深度解析与避坑指南
  • mysql 事务中如果有sql语句出错,会导致自动回滚吗?
  • 力扣刷题总表
  • 【Vue】 实现TodoList案例(待办事项)
  • Java高频面试之并发编程-10
  • C++之string
  • 如何在本地部署小智服务器:从源码到全模块运行的详细步骤
  • CA校验主辅小区配置及UE能力
  • 首发记忆行车方案与座舱智能管家,佑驾创新“抢跑”驾舱融合市场
  • 恒流恒压直流充电测试负载设计:构建精准化检测体系
  • 计算机基础:二进制基础14,二进制加法
  • 如何将二叉树展开为链表?两种Java实现方法对比
  • FPGA 38 ,FPGA 网络通信协议栈基础,ARP 协议深度解析与模块划分( ARP与以太网帧,以及ARP模块常用文件 )
  • 细说STM32单片机FreeRTOS互斥量及其编程实例
  • C# 导入EXCEL 报错外部表不是预期的格式错误指南方案