开发期页面闪烁与依赖预构建优化
概述
后台管理项目里大量页面使用 Element Plus 并结合自动导入插件按需引入组件,开发时第一次点击「基础表格」「复杂表格」「基础表单」等页面,常常会出现轻微白闪:控制台还在解析新的依赖和样式。这类问题的本质往往不是路由本身慢,也不是页面组件写得差,而是 Vite 在首次访问该模块时才开始补做依赖预构建和样式注入——浏览器先渲染出页面结构,再等依赖和样式补齐,视觉上就表现为白闪或闪烁。
本文从 Vite 与 Webpack 的开发期差异讲起,说明 optimizeDeps.include 如何预热高频依赖,如何用 glob 批量覆盖 Element Plus 的深层样式导入,以及如何避免把「开发期预构建」和「生产构建样式 external」混为一谈。
学习目标
- 区分「样式未就绪导致的白闪」和「页面自身的过渡动画」
- 理解 Vite 原生 ESM 按需加载与 Webpack 整包构建的开发期差异
- 用
optimizeDeps.include预热高频依赖,减少首次切页白闪 - 用 glob 模式一次性覆盖
element-plus/es/components/**/style/css这类深层导入 - 明确
optimizeDeps主要作用于开发期,生产样式 external 由入口导入和 CDN 策略决定
一、白闪的本质:首次访问才触发的依赖预构建
页面切换时的白闪,在使用 Element Plus、自动导入插件、路由按页面切分模块的项目里非常常见。根因是:当前页面依赖此前没有被预构建,组件样式也还没有进入缓存,浏览器先渲染出页面结构,再等依赖和样式补齐。
判断方法很直接:首次访问某页面明显闪烁,第二次访问变顺滑,通常就说明依赖缓存已经生效。这类问题主要发生在开发环境,不代表生产环境一定有同样表现,也不要把它和页面自身的过渡动画混淆。
二、Vite 与 Webpack 的开发期差异
理解这个问题前,要搞清楚两者处理依赖的方式不同:
- Webpack 开发期会先把依赖打进 bundle,再通过 HMR 做增量替换;
- Vite 开发期基于原生 ESM 提供源码,请求到哪个模块就处理哪个模块,并自动用 esbuild 做 dependency pre-bundling。
这也是为什么 Vite 首次启动通常更快,但第一次访问某些全新路由时,仍可能遇到「冷加载」——新深层导入会触发额外的预构建。不要把「Vite 很快」理解成「任意新路由第一次访问都零成本」。
三、optimizeDeps.include 预热高频依赖
optimizeDeps.include 是一类典型的开发体验优化配置。当你已经观察到某些依赖会在点开页面时才被处理,就可以把它们显式塞进 include,让 Vite 在更早的阶段就把这些依赖准备好。在这个案例里,最值得优先放进去的是 dayjs 和 Element Plus 的某些深层样式导入。
import { defineConfig } from "vite";
export default defineConfig({
optimizeDeps: {
include: [
"dayjs",
"element-plus/es/components/table/style/css",
"element-plus/es/components/form/style/css"
]
}
});include 不是越多越好,应优先放「高频访问且容易触发闪烁」的依赖。调整后如果效果不明显,先检查缓存是否还在沿用旧结果。
四、用 glob 批量覆盖深层样式导入
当项目里还有大量组件页、后续还会继续访问新组件时,手写几十条 style/css 路径显然不现实。Vite 官方文档已明确:optimizeDeps.include 支持 trailing glob pattern,可以一次性覆盖大量深层导入。
import { defineConfig } from "vite";
export default defineConfig({
optimizeDeps: {
include: [
"dayjs",
"element-plus/es/components/**/style/css"
]
}
});这里的 glob 主要解决「深层导入很多、不断触发重新预构建」的问题。当前官方推荐直接用 Vite 支持的 glob,不一定非要自己额外引 fast-glob。
五、optimizeDeps 主要是开发期能力,不要让它背生产 external 的锅
课程里提到「生产环境不应该再包含这些 style/css」,工程思路本身没错,但需要结合当前 Vite 版本说得更准确:optimizeDeps 主要用于开发期 dependency optimizer,从 Vite 5.1 开始,构建阶段的依赖预构建实验能力已经被移除。所以:
optimizeDeps不会直接决定生产打包是否把 CSS 打进去;- 生产环境样式是否外部化,主要看入口导入方式、CDN 配置和构建策略。
如果想在配置里区分开发和生产,更推荐写成「开发时启用 optimizeDeps,生产时不额外声明它」,这更多是出于配置清晰度,而不是为了控制生产打包结果。推荐用 command === "serve" 来表达「开发期优化」,比 isProd 更贴近 Vite 配置语义。
import { defineConfig } from "vite";
const devOptimizedDeps = [
"dayjs",
"element-plus/es/components/**/style/css"
];
export default defineConfig(({ command }) => {
const isServe = command === "serve";
return {
optimizeDeps: isServe
? { include: devOptimizedDeps }
: undefined
};
});六、改完配置记得清理预构建缓存
改完 vite.config.ts 后,如果效果没生效,最常见的原因是 node_modules/.vite 仍在使用旧缓存。Vite 会把预构建依赖缓存到这里,不清缓存,浏览器和本地都可能继续使用旧的预构建结果。官方给出的常见做法有两类:删除 node_modules/.vite,或使用 vite --force。
rm -rf node_modules/.vite
pnpm devpnpm vite --force正确操作顺序:修改 vite.config.ts → 删除 node_modules/.vite → 重启 dev server → 重新进入相关页面验证闪烁是否消失。如果浏览器也有强缓存,可顺手做一次硬刷新。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 某个页面第一次打开会白闪,第二次明显顺滑 | 首次访问时才触发依赖预构建和样式缓存 | 将相关依赖加入 optimizeDeps.include |
把 element-plus 写进 include 还是会闪 | 真正频繁触发的是深层样式导入,而非包名本身 | 补 element-plus/es/components/**/style/css |
改完 vite.config.ts 没效果 | node_modules/.vite 仍在使用旧缓存 | 删除缓存或使用 vite --force 后重启 |
| 把开发期白闪和生产构建体积混为一谈 | optimizeDeps 与生产 external 不是同一条链路 | 开发期看依赖预构建,生产期看入口导入和 CDN 策略 |
| 只优化了表格页,其他组件页还是闪 | include 覆盖范围不完整 | 优先改成 glob,而不是继续手写单个组件路径 |