Vue3 + SCSS 样式不生效问题深度解析

问题背景

在某管理平台时,遇到了一个奇怪的问题:

同一套 CSS 类 .filter-section,在某些页面生效,在另一些页面却不生效。

通过排查发现,问题出在 Vite + Vue3 + SCSS 的样式加载机制上。本文将详细分析问题根因,并给出解决方案。

现象观察

页面对比

页面.filter-section 是否生效<style> 块内容
pageA.vue✅ 生效有自定义样式
pageB.vue✅ 生效有自定义样式
pageC.vue不生效空的

关键代码

pageC.vue(不生效):

1
2
3
<div class="filter-section">...</div>

<style scoped lang="scss"></style>

pageA.vue(生效):

1
2
3
4
5
6
7
<div class="filter-section">...</div>

<style scoped lang="scss">
.cn-price-prefix::before {
content: "¥";
}
</style>

根因分析

Vite 配置

项目中 vite.config.js 的关键配置:

1
2
3
4
5
6
7
8
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler",
additionalData: '@use "@/style/style.scss" as *;',
},
},
},

additionalData 的作用是:在每个 <style lang="scss"> 块编译前自动注入代码

核心问题

空的 <style scoped lang="scss"> 块会被 Vite 跳过不处理!

执行流程对比:

有内容的 style 块:

1
2
3
4
5
1. Vite 检测到 <style scoped lang="scss">
2. additionalData 注入 @use "@/style/style.scss" as *;
3. 编译整个 style 块(包括注入的 @use
4. style.scss 中的 .filter-section CSS 规则被输出(带 scoped 属性)
5. 页面元素匹配到样式 ✅

空的 style 块:

1
2
3
4
5
1. Vite 检测到 <style scoped lang="scss"></style> 块
2. Vite 发现块为空,直接跳过不处理 ❌
3. additionalData 注入的 @use 从未被执行
4. .filter-section CSS 规则没有被输出
5. 页面元素无法匹配样式 ❌

@use vs @import 的区别

加载机制

特性@use@import
加载方式模块化加载,只加载一次文本注入,每次都注入
重复加载同一文件只编译一次多次导入会重复编译
CSS 规则✅ 会导入✅ 会导入
命名空间默认有,可用 as * 取消无命名空间
官方状态✅ 推荐使用❌ 已废弃(deprecated)

常见误区

❌ 错误认知:@use 不会导入 CSS 规则,只导入变量/mixin/function

✅ 正确认知:@use 导入 CSS 规则,它与 @import 的区别在于命名空间和加载机制,而非是否导入 CSS 规则。

作用范围

style.scss 内容:

1
2
3
4
5
6
$theme-color: #26b165; // 变量
.filter-section {
// CSS 规则
width: 100%;
margin-bottom: 20px;
}

使用 @use 导入后:

  • ✅ 可以使用 $theme-color 变量
  • .filter-section 规则会生效

使用 @import 导入后:

  • ✅ 可以使用 $theme-color 变量
  • .filter-section 规则会生效

解决方案

方案一:全局导入(推荐)

main.js 中全局导入样式文件:

1
import "./style/style.scss"; // 全局导入,所有页面生效

优点:

  • .filter-section 只输出一次,不会导致 CSS 重复膨胀
  • 所有页面(包括空 style 块的页面)都能使用
  • 符合 CSS 最佳实践

缺点:

  • 需要额外配置

方案二:添加空规则(临时方案)

给空的 style 块添加任意规则:

1
2
3
4
<style scoped lang="scss">
.empty-rule {
}
</style>

原理:让 Vite 认为 style 块有内容,从而正常编译。

缺点:

  • 每个有非空 style 块的组件都会输出一份 .filter-section 规则(带 scoped 属性)
  • 导致 CSS 重复膨胀
  • 代码不够优雅

方案三:拆分样式文件(进阶方案)

将变量和 CSS 规则分离:

1
2
3
// _variables.scss(只包含变量、mixin、function)
$theme-color: #26b165;
$border-color: #ebeff0;
1
2
3
4
5
// _common.scss(只包含 CSS 规则)
.filter-section {
width: 100%;
margin-bottom: 20px;
}

vite.config.js:

1
2
3
scss: {
additionalData: '@use "@/style/_variables.scss" as *;',
}

main.js:

1
import "./style/_common.scss";

优点:

  • 变量通过 @use 注入,CSS 规则全局导入
  • 不会重复输出 CSS 规则
  • 代码结构清晰

最佳实践总结

样式文件组织

1
2
3
4
src/style/
├── _variables.scss # 变量、mixin、function(通过 @use 注入)
├── _common.scss # 公用 CSS 规则(全局导入)
└── style.scss # 合并文件(包含变量和规则)

Vite 配置

1
2
3
4
5
6
7
8
9
css: {
preprocessorOptions: {
scss: {
api: "modern-compiler",
// 只注入变量文件
additionalData: '@use "@/style/_variables.scss" as *;',
},
},
},

入口文件配置

1
import "./style/_common.scss"; // 全局导入 CSS 规则

总结

问题根因

空的 <style scoped lang="scss"> 块被 Vite 跳过不处理,导致 additionalData 注入的 @use 从未被执行。

解决方案

推荐使用全局导入方案,在 main.js 中导入包含 CSS 规则的样式文件。

关键要点

  1. @use@import 都会导入 CSS 规则
  2. additionalData 只对非空的 style 块生效
  3. 全局导入是最可靠的样式加载方式
  4. 分离变量和 CSS 规则是最佳实践

附录:调试技巧

查看编译后的 CSS

在浏览器开发者工具中:

  1. 打开 Elements 面板
  2. 查看 .filter-section 元素的样式
  3. 如果没有 .filter-section 相关样式,说明该样式未被输出

验证空 style 块假设

给空的 style 块添加一条规则,观察样式是否生效:

1
2
3
4
5
<style scoped lang="scss">
.debug-rule {
background: red;
}
</style>

如果生效,说明问题确实出在空 style 块上。

本文基于实际项目经验整理,希望能帮助遇到类似问题的开发者。