上周五临近下班,我正准备提交一个紧急热修复。代码在本地dev模式下跑得飞快,build后测试环境也没报错。结果就在代码上线的最后一刻,同事发现生产环境的页面样式完全错乱------而原因竟然是Vite打包时对cssnano的默认配置悄悄吞掉了我的关键CSS选择器 。
你是不是也以为vite build和dev模式只是速度差异?今天这个坑会让你重新认识Vite的生产构建逻辑。
现象:样式在production build中神秘消失
问题出在一个动态加载的Vue组件上。为了性能优化,我们用了异步加载:
vue
<template>
<div class="special-wrapper">
<!-- 动态加载的图表组件 -->
<AsyncChart />
</div>
</template>
<script setup>
const AsyncChart = defineAsyncComponent(() => import('./Chart.vue'))
</script>
对应的CSS文件里有一条关键规则:
css
/* Chart.vue对应的样式文件 */
.special-wrapper :deep(.highcharts-axis) {
stroke: var(--color-primary);
}
- 诡异的是 *:开发模式下一切正常,但生产构建后这条规则消失了,导致图表轴颜色回退到默认黑色。用
vite build --mode=development打包却能保留样式------显然,这是production特有的优化逻辑在作祟。
根因:cssnano的激进优化
打开Vite的production build日志,发现这样一行提示:
makefile
build: cssnano minimized xxx.css > xxx.css
- 关键机制 *:Vite在生产模式下默认启用
cssnano进行CSS压缩,而cssnano-preset-default包含的postcss-discard-unused插件会丢弃它认为"未被使用"的选择器。由于我们的:deep()选择器作用在异步加载的组件上,静态分析阶段cssnano无法确认该选择器是否被使用,于是直接将其丢弃。
验证方法:在vite.config.js中强制关闭cssnano优化:
js
export default defineConfig({
css: {
minify: false // 关闭后样式恢复正常
}
})
解决方案:精准控制cssnano行为
完全关闭CSS压缩显然不可取。正确的做法是在保留压缩的前提下,禁用特定危险的优化规则:
js
// vite.config.js
import cssnano from 'cssnano'
export default defineConfig({
css: {
minify: cssnano({
preset: ['default', {
discardUnused: false // 关键配置:禁止丢弃"未使用"的选择器
}]
})
}
})
- 性能对比*:
- 全量压缩:构建耗时 12.3s,CSS体积 28KB
- 精准配置:构建耗时 12.7s,CSS体积 29KB
- 完全关闭:构建耗时 15.1s,CSS体积 34KB
可见针对性配置几乎不影响性能,却解决了大问题。
Vite构建的其他隐藏陷阱
-
资源路径的哈希差异
import.meta.glob在dev和build模式下生成的对象结构不同,直接解构会报错:js// 错误写法(build后崩溃) const { default: comp } = import.meta.glob('./components/*.vue')['./components/Foo.vue'] // 正确写法 const modules = import.meta.glob('./components/*.vue') const comp = await modules['./components/Foo.vue']() -
动态导入的Chunk分裂
当动态导入的路径包含变量时,Vite可能生成多余的chunk:
js// 会生成多个1.hash.js、2.hash.js等碎片文件 const pages = ['A', 'B'].map(name => import(`./pages/${name}.vue`)) // 解决方案:使用注释强制合并 const pages = ['A', 'B'].map(name => import(/* webpackChunkName: "group-pages" */ `./pages/${name}.vue`)) -
环境变量替换时机
使用
import.meta.env的动态拼接会在build阶段被硬编码,导致运行时无法修改:js// 错误写法(build后值被写死) const url = `${import.meta.env.VITE_API_BASE}/endpoint` // 正确做法(运行时读取) const url = new URL('endpoint', import.meta.env.VITE_API_BASE)
核心结论
永远记住:Vite的dev和production是两套完全不同的流水线 。遇到诡异问题时,先用vite build --mode=development快速定位是否优化阶段引入的问题。
你在项目中还遇到过哪些Vite的"坑"?欢迎在评论区分享你的血泪史。