Vite 构建优化中的五个常见反模式:别让构建反而变慢
Vite 构建优化中的五个常见反模式别让构建反而变慢一、Vite 的「快」不是免死金牌Vite 的开发服务器启动速度和 HMR 性能在社区已经有口皆碑。但很多人忽略了一个事实Vite 的开发体验快不代表生产构建也一定快。在不恰当的优化下Vite 的生产构建速度甚至可能比 Webpack 更慢。根本原因在于Vite 开发模式使用 esbuild 做预构建毫秒级生产模式使用 Rollup 做打包秒级到分钟级。两者的速度不在一个数量级上。如果开发者将在开发模式下的体验等同于生产构建的性能就会忽视生产构建中的性能瓶颈。以下是在实际项目中反复踩过的五个反模式。二、反模式一无差别使用手动 Chunk 拆分Vite 默认不使用manualChunks时Rollup 会自动根据模块依赖图做合理的 Chunk 拆分。但很多开发者从 Webpack 迁移过来后习惯性地手动配置manualChunks结果反而破坏了 Rollup 的自动优化。// 反模式照搬 Webpack 的 splitChunks 逻辑到 Vite // vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { // 将 react 全家桶放在一起 —— 看起来合理但坑很大 react-vendor: [react, react-dom, react-router-dom], ui-vendor: [antd, ant-design/icons], // 将 utils 单独打包 —— 这些小 chunk 反而是负担 utils: [lodash, dayjs], }, }, }, }, });问题在于过多的 vendor chunk 增加 HTTP 请求数HTTP/2 虽然支持多路复用但并不意味着无限并发就是最优解。20 个小型 vendor chunk 可能比 3-5 个中型 chunk 的加载速度更慢。Chunk 之间的重复依赖手动分组如果配置不当会导致同一个依赖出现在多个 chunk 中。缓存失效频率增加如果将一个高频变化的业务模块和一个低频变化的第三方库放在同一个 chunk 中每次业务代码变更都会导致整个 chunk 缓存失效。正确做法// 要么不配置 manualChunks信任 Rollup 的自动优化 export default defineConfig({ build: { rollupOptions: { output: { // 不设置 manualChunks让 Rollup 自动决定 }, }, }, }); // 如果需要精细控制用函数式配置做条件判断 export default defineConfig({ build: { rollupOptions: { output: { manualChunks(id: string) { // 只对体积 100KB 且不常变化的库做独立分包 if (id.includes(node_modules)) { // React 核心单独一个 chunk体积大、频率低 if (id.includes(node_modules/react/) || id.includes(node_modules/react-dom/) || id.includes(node_modules/scheduler/)) { return react-core; } // 大型 UI 库单独一个 chunk if (id.includes(node_modules/antd/)) { return antd; } // 其余所有 node_modules 合并到 vendor避免分包过细 return vendor; } // 业务代码不手动分包 }, }, }, }, });衡量标准用rollup-plugin-visualizer分析分包结果。理想的 chunk 分布是chunk 数量在 5-10 个之间每个 chunk 的体积在 50KB 到 200KBgzipped之间。三、反模式二CSS 处理的性能陷阱Vite 对 CSS 的处理在开发和生产模式下行为不同。开发模式直接注入style标签速度极快。生产模式下CSS 需要经过 PostCSS 处理、提取、压缩。如果 PostCSS 配置不当CSS 处理可以消耗 30% 以上的总构建时间。// 反模式加载了太多 PostCSS 插件 // postcss.config.js export default { plugins: [ require(postcss-import), require(postcss-nested), require(autoprefixer), require(postcss-preset-env), require(cssnano)({ preset: default }), // 已经在 PostCSS 处理中压缩 require(postcss-pxtorem), // 如果不需要 rem 转换则多余 require(postcss-sort-media-queries), // 排序媒体查询 ], };每个插件都在增加处理时间。在 500 个 CSS Module 文件的项目中多一个不必要的 PostCSS 插件可能增加 3-5 秒的构建时间。优化策略// 精简 PostCSS 配置 —— 只保留必需的插件 export default { plugins: [ require(postcss-import), // 处理 import有必要 require(postcss-nested), // 如果使用了嵌套语法保留 require(autoprefixer), // 浏览器兼容前缀有必要 // 注意不要在使用 Vite 时额外配置 cssnano // Vite 内部已使用 esbuild 对 CSS 进行压缩 // 双重压缩浪费 CPU 且不提升效果 ], }; // 生产构建时的 CSS 压缩由 Vite 内置的 esbuild 处理 // build.cssMinify 默认 esbuild不需要额外配置 export default defineConfig({ build: { cssMinify: esbuild, // 默认值比 cssnano 快 20-30 倍 }, });关键差别压缩工具速度压缩率Vite 默认esbuild (cssMinify)极快95%是cssnano慢 20x98%否lightningcss快 2x97%可选除非对那额外的 3% 压缩率有极端需求否则不要切到 cssnano。四、反模式三图片资源的内联与 Base64 滥用Vite 默认对小于 4KB 的图片做 Base64 内联。这个阈值在很多项目中是合理的。但在以下场景会导致问题一个组件中有 10 张小图标每个 3KB全部内联 → HTML/JS 体积增加 30KB。一张重复使用的图标内联后出现在 5 个 chunk 中 → 实际增加 15KB 而非 3KB。// 反模式不加区分地将所有小图内联 export default defineConfig({ build: { assetsInlineLimit: 20480, // 20KB几乎所有图标都被内联了 }, });优化策略// 按使用频率和体积分层处理 export default defineConfig({ build: { // 默认 4KB 是一个合理的阈值 assetsInlineLimit: 4096, rollupOptions: { output: { // 对图片资源使用内容哈希命名最大化缓存效率 assetFileNames: (assetInfo) { if (assetInfo.name?.endsWith(.svg)) { return assets/svg/[name]-[hash][extname]; } if (/\.(png|jpe?g|gif|webp|avif)$/.test(assetInfo.name ?? )) { return assets/images/[name]-[hash][extname]; } return assets/[name]-[hash][extname]; }, }, }, }, }); // 对于 SVG 图标使用 SVG Sprite 方案而非逐个内联 // vite-plugin-svg-icons 或 neodx/svg 可以在构建时生成 Sprite更优方案对于图标类资源优先使用 SVG Sprite use标签。一个 Sprite 文件包含全部图标一次加载全局复用。比逐个内联 Base64 减少 60% 到 80% 的总体积。五、反模式四过量的依赖预构建Vite 的依赖预构建optimizeDeps将 CommonJS/UMD 模块转换为 ESM 以加速开发服务器启动。但如果将不需要预构建的包也加入include列表会导致预构建时间膨胀。// 反模式盲目扩大预构建范围 export default defineConfig({ optimizeDeps: { include: [ react, react-dom, react-router-dom, antd, ant-design/icons, lodash, lodash-es, dayjs, axios, zustand, echarts, d3, three, // 大型库全部预构建 my-org/utils, my-org/hooks, // 自己的包也加进去 ], }, });问题预构建的依赖越多node_modules/.vite目录越大首次启动越慢。而且某些大型库echarts、three.js的预构建可能耗时 15 秒以上。// 正确做法只预构建必需的包 export default defineConfig({ optimizeDeps: { // include显式包含 Vite 没有自动发现的依赖 // 通常是动态 import 的依赖或在 HTML 中直接引用的模块 include: [ // Vite 自动发现失败的才需要手动添加 // react, react-dom 等会自动被 Vite 发现不需要手动 include ], // exclude排除不需要预构建的包 // 如果某个包已经是 ESM 格式且没有大量子模块排除它可以加速 exclude: [ // 已经 ESM 化的小型工具库 lodash-es, // 已经是 ESM不需要转换 ], // esbuild 选项限制转换行为 esbuildOptions: { // 不加载不必要的 loader loader: { .js: jsx, }, }, }, });何时需要手动include在index.html中通过script typemodule直接引用的模块。通过import()动态引入且 Vite 首次扫描时未访问到的模块。Monorepo 中通过 workspace 链接的本地包Vite 有时无法自动发现。六、反模式五开发和生产配置的混淆Vite 允许通过mode区分开发和生产。但很多项目在defineConfig中写了大量条件判断逻辑使配置文件变得难以维护。更糟糕的是一些只在开发环境下生效的插件如vite-plugin-react-inspector在生产构建中仍然被加载增加构建时间。// 反模式一个配置文件打天下到处是条件判断 export default defineConfig(({ mode }) { const isDev mode development; return { plugins: [ react(), isDev inspector(), // 开发才需要 isDev reactRefresh(), // 开发才需要 !isDev visualizer(), // 分析工具 legacy({ targets: [defaults] }), // 生产才需要 ].filter(Boolean), build: { minify: isDev ? false : esbuild, sourcemap: isDev, }, }; });正确做法拆分配置文件。// vite.config.ts —— 公共配置 import { defineConfig } from vite; import react from vitejs/plugin-react; export default defineConfig({ plugins: [react()], resolve: { alias: { : /src } }, }); // vite.config.dev.ts —— 开发环境覆盖 import { defineConfig, mergeConfig } from vite; import baseConfig from ./vite.config; import inspector from vite-plugin-react-inspector; export default mergeConfig(baseConfig, defineConfig({ plugins: [inspector()], server: { port: 3000, open: true, }, })); // vite.config.prod.ts —— 生产环境覆盖 import { defineConfig, mergeConfig } from vite; import baseConfig from ./vite.config; import { visualizer } from rollup-plugin-visualizer; import viteCompression from vite-plugin-compression; export default mergeConfig(baseConfig, defineConfig({ plugins: [ visualizer({ gzipSize: true, brotliSize: true }), viteCompression({ algorithm: brotli }), ], build: { sourcemap: false, minify: esbuild, rollupOptions: { output: { manualChunks: { /* ... */ }, }, }, }, })); // package.json —— 通过 --config 指定不同配置文件 // dev: vite --config vite.config.dev.ts, // build: vite build --config vite.config.prod.ts,七、构建性能诊断清单不要凭直觉判断构建慢在哪里。用数据说话# 1. 启用构建分析 DEBUGvite:build npx vite build # 2. 使用 rollup-plugin-visualizer 分析包体积 # 在 vite.config.ts 中临时添加 import { visualizer } from rollup-plugin-visualizer; plugins: [visualizer({ open: true, gzipSize: true })] # 3. 检查依赖大小 npx vite-bundle-visualizer # 4. 分析重复依赖 npx depcheck # 5. 检测大文件 find src -type f -name *.tsx -o -name *.ts | xargs wc -l | sort -rn | head -20构建阶段典型耗时占比你可以做什么Rollup 打包50-70%减少依赖、拆分包合理性CSS 处理10-30%精简 PostCSS 插件资源复制5-15%使用 public 目录、减少大文件Terser/压缩5-15%使用 esbuild minify类型检查单独运行不在 build 阶段做五、总结Vite 构建优化五个反模式的核心要点信任 Rollup 的自动分包不要照搬 Webpack 的 splitChunks 逻辑只在体积 100KB 且低频变化的库做独立分包。PostCSS 只保留必需插件Vite 内置 esbuild CSS 压缩比 cssnano 快 20 倍双重压缩浪费 CPU 且不提升效果。SVG 图标用 Sprite 方案比逐个 Base64 内联减少 60-80% 总体积4KB 内联阈值对多数项目已足够合理。依赖预构建最小化只对 Vite 未自动发现的依赖做 include排除已是 ESM 的小型库如 lodash-es。拆分 dev/prod 配置文件不要在单文件中堆叠 mode 条件判断通过--config指定不同环境配置。可执行建议本周用rollup-plugin-visualizer分析当前分包结果如果 chunk 数超过 15 个或存在 30KB 的小 chunk说明手动分包过度应回归 Rollup 默认策略。八、总结Vite 构建优化的五个反模式手动 Chunk 拆分信任 Rollup 的自动优化仅在需要精细控制时用函数式manualChunks。过多 PostCSS 插件只保留必需的生产构建用 esbuild 的 CSS 压缩而非 cssnano。Base64 内联滥用区分资源内联阈值SVG 图标优先使用 Sprite 方案。过量依赖预构建只对 Vite 未自动发现的依赖做include排除已是 ESM 的小型库。配置混乱拆分 dev/prod 配置文件不要在单文件中用mode条件判断。核心原则Vite 的默认配置已经是最佳实践的起点。在添加任何优化之前先用构建分析工具量化当前的性能数据然后针对性地改进瓶颈。盲目优化往往使构建更慢而非更快。

相关新闻

设计系统 AI 化的 ROI 评估:成本、收益与不可量化价值的衡量

设计系统 AI 化的 ROI 评估:成本、收益与不可量化价值的衡量

设计系统 AI 化的 ROI 评估:成本、收益与不可量化价值的衡量 一、引子:CTO 问的那句话 "引入 AI 辅助设计系统后,我们省了多少人天?" 这个问题我回答不了。不是因为没做数据统计,而是因为 AI 化带来的最大价…

2026/10/18 8:09:50 阅读更多 →
Rust首次闯入TIOBE前十:系统级编程语言的历史性时刻与2026工程实践全景

Rust首次闯入TIOBE前十:系统级编程语言的历史性时刻与2026工程实践全景

Rust首次闯入TIOBE前十:系统级编程语言的历史性时刻与2026工程实践全景 2026年7月,编程语言江湖迎来了一声惊雷。在TIOBE编程社区指数最新发布的2026年7月榜单中,Rust以1.34%的评分首次跻身前十,排名第10位。这是Rust语言诞生16年…

2026/10/18 8:10:04 阅读更多 →
UE5 C++开发:深入理解GEngine全局对象的核心功能与实战应用

UE5 C++开发:深入理解GEngine全局对象的核心功能与实战应用

1. 项目概述:为什么需要深入理解GEngine?在UE5的C开发中,我们每天都在和各种Actor、Component、蓝图节点打交道,但有一个全局性的、几乎无处不在却又容易被新手忽略的核心对象——GEngine。它不是某个具体的游戏角色,也…

2026/10/18 8:10:04 阅读更多 →

最新新闻

free-claude-code 开源项目拆解:AI 编程助手的可控性设计与工程实践

free-claude-code 开源项目拆解:AI 编程助手的可控性设计与工程实践

1. 这个项目到底在解决什么问题第一次看到 "free-claude-code" 这个标题,我脑子里蹦出来的第一个念头是:这又是一个想给 AI 编程助手做"平替"或者"增强层"的东西。事实也确实如此。这个项目的核心定位,是围绕 …

2026/10/19 1:35:19 阅读更多 →
10 分钟跑通第一个场景:Habitat-Sim 具身智能 3D 模拟器实战指南

10 分钟跑通第一个场景:Habitat-Sim 具身智能 3D 模拟器实战指南

10 分钟跑通第一个场景:Habitat-Sim 具身智能 3D 模拟器实战指南 【免费下载链接】habitat-sim A flexible, high-performance 3D simulator for Embodied AI research. 项目地址: https://gitcode.com/GitHub_Trending/ha/habitat-sim Habitat-Sim 是一款面…

2026/10/19 1:35:19 阅读更多 →
【WebSocket】 WebSocket 是否支持压缩?如何启用 permessage-deflate 扩展?

【WebSocket】 WebSocket 是否支持压缩?如何启用 permessage-deflate 扩展?

WebSocket 压缩深度解析:permessage-deflate 扩展的原理与实战应用 用户问题原文:WebSocket 是否支持压缩?如何启用 permessage-deflate 扩展? 在构建直播互动弹幕系统时,我们曾因未启用 WebSocket 压缩导致带宽成本飙升 300%,单场热门直播的 CDN 费用高达数万元。根本原…

2026/10/19 1:35:19 阅读更多 →
【项目】【在线判题系统】c端:用户退出登陆与获取当前用户信息

【项目】【在线判题系统】c端:用户退出登陆与获取当前用户信息

目录一、退出登录1.1 controller1.2 service二、获取当前用户信息2.1 controller2.2 service三、修改3.1 token创建3.2 参数修改3.3 传参四、前端一、退出登录 与前面的管理员的退出登录没有区别,注销token即可。 1.1 controller oj-modules/oj-friend/src/main/…

2026/10/19 1:35:19 阅读更多 →
【Android面试】RecylerView专题

【Android面试】RecylerView专题

文章目录一、基础原理与复用缓存1. RecyclerView 与 ListView 的核心区别是什么?2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑&…

2026/10/19 1:35:19 阅读更多 →
黑通道功能安全协议:从原理到落地的完整指南

黑通道功能安全协议:从原理到落地的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 1:34:19 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →