一、问题背景:同一套代码,dev 没样式,build 却有
在一次 Vite 7 项目开发中,遇到了一个看似"反直觉"的问题:
-
npm run dev时,页面上某个 class 已存在,但对应样式不生效 -
npm run build后,样式却正常生效 -
该样式并非当前路由引入,而是在另一个路由中通过
import './index.scss'引入
这类问题在早期 Vite 或小型项目中不易暴露,但在 路由拆分、代码分割、布局复用 场景下,会频繁出现。
二、问题表象分析:不是 class 丢了,而是 CSS 根本没加载
通过 DevTools 检查可以发现:
-
DOM 结构中,
class="page-wrap"已正确挂载 -
但在 dev 模式下:
-
样式面板中找不到
.page-wrap -
页面中对应的
<style>或<link>根本不存在
-
-
而在 build 后:
-
.page-wrap存在于最终打包的 CSS 文件中 -
样式全局生效
-
这说明一个关键事实:
问题不在模板,也不在 class,而在 CSS 是否被加载。
三、根本原因:Vite dev 与 build 的 CSS 处理机制不同
3.1 dev 模式:CSS 依附于 JS 执行路径
在 npm run dev 模式下,Vite 的行为是:
-
CSS 与 JS 模块强绑定
-
只有当 JS 模块被执行时:
- 该模块中
import的 CSS 才会被注入到页面
- 该模块中
-
未执行的模块:
- 对应 CSS 根本不会进入页面
也就是说:
// 某个路由页面
import './index.scss'
如果当前并未进入该路由:
-
该 JS 文件不会被执行
-
该
index.scss不会被加载 -
样式在 dev 下完全不存在
3.2 build 模式:Rollup 会提前收集并提升 CSS
npm run build 阶段,Vite 使用 Rollup 进行打包,CSS 行为发生了本质变化:
-
Rollup 会静态分析完整依赖图
-
只要某个 CSS 文件被 import 过:
- 就会被收集进最终的 CSS chunk
-
最终生成的 CSS 文件:
-
不再关心"这个样式原本属于哪个路由"
-
而是整体提升为全局样式
-
因此就会出现:
dev 阶段缺样式
build 阶段样式却"自动好了"
这并不是 bug,而是两种模式的设计目标不同。
四、为什么 Vite 7 更容易暴露这个问题
相较于早期版本,Vite 7 在 dev 模式下:
-
CSS 模块图更严格
-
不会"顺手"加载未执行模块的样式
-
HMR 和样式隔离更明确
这使得一些 原本语义是"全局样式" ,却被错误放在 路由级模块中引入 的代码,问题被直接放大。
五、问题本质:CSS 语义层级 与 JS 执行层级不匹配
这类问题的根因,并不在于:
"不要在 JS 中 import CSS"
而在于:
不要在「非稳定执行路径」中,定义「全局语义的样式」
5.1 什么是非稳定执行路径
典型高风险场景包括:
-
路由懒加载页面
-
defineAsyncComponent -
条件分支中 import
-
生命周期中动态 import
这些 JS 模块:
-
在 dev 下不一定会执行
-
但在 build 阶段,其 CSS 却会被提前收集
5.2 .page-wrap 属于哪一类样式
从样式内容和使用位置来看:
.page-wrap {
width: 100%;
height: 100%;
display: flex;
}
它明显属于:
-
页面骨架
-
布局容器
-
跨路由复用的结构样式
这类样式不应依附于某一个具体路由页面。
六、正确的解决思路:按"样式语义"而非"代码位置"组织 CSS
6.1 全局 / 布局级样式
应放在:
-
main.ts -
App.vue -
或稳定存在的 Layout 组件中
例如:
// main.ts
import '@/styles/layout.scss'
或:
// Layout.vue
import './layout.scss'
6.2 路由级样式
只作用于该页面本身:
// ChatPage.vue
import './chat-page.scss'
并确保:
-
不包含全局布局语义
-
不被其他页面依赖
6.3 组件级样式
组件内部私有样式:
<script setup>
import './index.scss'
</script>
这是完全推荐的写法。
七、一套可落地的样式分层规范(Vite 7)
styles/
├─ reset.scss // main.ts
├─ theme.scss // main.ts
├─ layout.scss // Layout.vue
├─ page/
│ ├─ chat.scss
│ └─ home.scss
└─ components/
├─ button.scss
└─ modal.scss
核心原则只有一句:
CSS 的"作用范围"必须 ≥ JS 的"执行范围"
八、总结
-
dev 缺样式、build 正常,并非 Vite bug
-
而是 dev 与 build 在 CSS 加载策略上的设计差异
-
问题的本质是:
把全局语义的 CSS,放进了非稳定执行的 JS 模块中 -
在 Vite 7 下,这类问题会被更清晰地暴露出来
正确的做法不是回避 import CSS,而是:
按样式语义分层,而不是按"写在哪个文件里"分层