ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue项目Sass/SCSS实战指南:从安装配置到暗黑主题

Vue项目Sass/SCSS实战指南:从安装配置到暗黑主题 如果你刚接触 Vue 项目大概率会在某个样式需求推进不下去的时候被人建议一句用 Sass 吧。你打开终端敲下这行命令npm install node-sass --save-dev运气好的人滚动几秒后装完了运气不好的人红字报错刷满一屏什么node-gyp、Python、binding.node、ERR! code 1看得人头皮发麻。更麻烦的是网上搜到的各种教程互相打架有的让你改vue.config.js有的让你在vite.config.ts里配置有的说用data有的说用prependData还有的让你直接::v-deep搞定样式穿透。到底听谁的这篇文章就是我长期在 Vue 项目里使用 Sass统一用 scss 语法攒下来的一整套经验覆盖 Vue2 Webpack 和 Vue3 Vite 两条技术路线讲清楚安装、配置、组件内写法、全局变量注入以及暗黑主题这类常见需求怎么落地。不扯空理论全是能直接抄走的实操方案。1. 为什么要在 Vue 项目里引入 Sass先算清楚这笔账先说一个容易混淆的点Sass 和 SCSS 到底是什么关系。Sass 是一门预处理语言一开始用的是缩进式语法长这样$primary-color: #1890ff .card color: $primary-color :hover color: darken($primary-color, 10%)后来为了让更多 CSS 开发者零成本上手Sass 又出了一套兼容 CSS 的大括号语法这就是 SCSS$primary-color: #1890ff; .card { color: $primary-color; :hover { color: darken($primary-color, 10%); } }Vue 项目里我们绝大多数情况下用的都是 SCSS因为你在style scoped里写的 CSS 代码只需要把$variable、mixin、include这些语法加进去其他写法和平常完全一样复制粘贴一份现成的 CSS 进来改个扩展名就能编译。那 Sass 到底解决了 Vue 开发里的什么痛点我觉得最核心的是这三件事。第一件设计变量能真正一改全改。Vue 组件化以后颜色、间距、圆角、字体大小这些设计 token 分散在几十上百个组件里。原生 CSS 当然也有变量var(--xxx)但它是运行时的语法略显啰嗦而且需要一个根节点承载。SCSS 的$variable是编译期的编译完成直接替换成字面量写起来省事配合!default还能优雅地给第三方库覆盖默认值。实际项目里我经常两个一起用SCSS 变量负责“编译期算值”CSS 变量负责“运行时换肤”这个后面讲暗黑主题时会展开。第二件嵌套写法让结构一目了然。组件样式最痛苦的是一长串类名选择器.modal-wrapper .modal-content .modal-header .close-btn { ... }嵌套写法可以直接复刻 DOM 结构.modal { -wrapper { padding: 16px; } -content { display: block; } -header { font-size: 16px; } }虽然 BEM 命名法能缓解部分问题但嵌套依旧是最直观、维护成本最低的方式。配合父选择器伪类、状态、元素组合写起来非常顺手。第三件Mixin 和函数把复用做到极致。做项目久了你会发现有些样式组合反复出现一行文字省略、三角形箭头、滚动条美化、毛玻璃效果。用 Mixin 封装一次全局任意组件include一下就行。最典型的例子mixin ellipsis($lines: 1) { overflow: hidden; text-overflow: ellipsis; if $lines 1 { display: -webkit-box; -webkit-line-clamp: $lines; -webkit-box-orient: vertical; } else { white-space: nowrap; } }在业务组件里一个include ellipsis(2)就能把两行省略搞定。你说这些东西不用 Sass、硬写可不可以当然可以。但项目一旦上了规模维护成本和重复劳动就完全不一样了。这笔账算清楚再动手不迟。2. 版本陷阱为什么很多人的安装失败和 node-sass 有关我在帮人排查问题时发现十个人里至少有五六个安装 Sass 失败都是栽在版本配套上还没进入配置环节就阵亡了。先科普一个背景node-sass这个包是 LibSass 的 Node 封装LibSass 是用 C 写的安装时会根据你本地的 Node 版本下载对应的预编译二进制文件。一旦下载不到匹配版本就会当场触发源码编译然后你的电脑必须要有 Python、C 编译工具链少一个都报错。而 Dart Sass就是 npm 上的sass包是纯 Dart 编译的官方直接给你打包成了通用二进制安装省心得多而且 LibSass 官方已经宣布弃用了。所以我的第一个建议新项目一律用sass包别碰node-sass。老项目已经在用node-sass的短期不想动可以理解但新建项目绝对不要给自己挖这个坑。接下来是sass-loader的版本问题。这玩意儿是给 Webpack 用的Vite 项目不需要它但很多教程没讲清楚导致 Vue3 Vite 项目里也被人硬塞了一个sass-loader虽然不至于报错但纯属多余。版本配套关系可以参照这张表项目类型构建工具版本推荐的 sass-loader 版本需要安装的包Vue 2 Vue CLI 4Webpack 4sass-loader10 及以下sasssass-loaderVue 2 Vue CLI 5Webpack 5sass-loader13 或最新sasssass-loaderVue 3 ViteVite 3/4/5不需要只需安装sassVue 3 WebpackWebpack 5sass-loader13 或最新sasssass-loader为什么这么严格因为sass-loader底层需要依赖 Webpack 的 loader API。Webpack 4 接不住太新的 loader装个sass-loader13进去直接给你报this.getOptions is not a function之类莫名其妙的错误。反过来老 loader 在新 Webpack 上也会出现 API 不兼容的问题。再说一个很多人忽略的坑Node 版本和sass包版本也会因为二进制平台不匹配出问题。尤其是 Dart Sass 在 1.79 之后对 Node 版本有了更高要求你要是还跑着 Node 14装最新版sass可能直接跑不起来。保守的做法是装sass1.63.0这一档的老版本兼容性好、稳定性高。如果你在云效、GitLab CI 这类自动构建环境里跑Docker 基础镜像的 Node 版本更要盯紧。还有一个典型的翻车场景项目里原本记着node-sass你图省事直接卸载安装sass但sass-loader配置里可能写了additionalData里面用了import ~bootstrap/scss/bootstrap;这类依赖~前缀找模块的写法。sass-loader在较新版本里对~前缀的解析策略有变化编译时就会爆红。遇到这种要把~去掉直接写成use /styles/bootstrap.scss as *;靠 Webpack 的 alias 解析。所以在动手安装之前先确认三件事你的 Node 版本是多少、构建工具是 Webpack 还是 Vite、Webpack 主版本是 4 还是 5。这三个答案确定了再往下走基本不会出大乱子。3. 安装与配置实操Vue2 加 Webpack 和 Vue3 加 Vite 两条路线下面直接给出我在真实项目里最常用、经过反复验证的命令和写法。3.1 Vue3 Vite 路线Vite 对预处理器的支持做得非常体贴它内置了 SCSS/SASS/LESS 的编译能力只是需要你把对应的预处理器包装上。官方推荐用sass命令只有一条npm install -D sass如果是 pnpm 或者 yarnpnpm add -D sass # 或者 yarn add -D sass --dev装完以后你在任一文件里写style langscss都不用额外配置 Vite它就能直接编译。如果你需要全局注入变量才需要动vite.config.ts这个文件下一章节细讲。这里要注意一个细节node_modules里会不会同时存在sass和sass-embedded这类变体如果你用了某些组件库它底层依赖了sass-embedded可能导致你的 Vite 预处理器加载异常报一个 “Thesasspackage is missing” 或者 Sass 版本冲突的错误。解决办法就是统一用sass包或者统一用sass-embedded不要混合。3.2 Vue2 Webpack 路线如下命令适用于 Vue CLI 4Webpack 4npm install -D sass1.63.0 sass-loader10如果你用了 Vue CLI 5Webpack 5npm install -D sass sass-loader13装完后我还建议顺手验证一下安装结果别急着写代码。用 Node 跑一段最简单编译测试node -e const sass require(sass); console.log(sass.compileString($a: 1px;.x{width:$a}).css)如果这个命令能正常输出.x{width: 1px;}说明任意写一堆 test 文件都没问题了。还有一个经受过大量团队检验的安装技巧如果你之前的项目没用锁依赖版本package.json里一下子多出十几个 Sass 相关版本各种不同 loader 的依赖树互相干扰推荐先删掉锁定文件重装rm -rf node_modules package-lock.json # 或者 pnpm-lock.yaml / yarn.lock npm install在 npm 生态里依赖树里并存两个sass版本很常见也会 “没理由” 地给你报错。这种时候从零重装一次问题通常会消失得无影无踪。注意如果项目已经开发过半重装前确认代码没有丢失风险且有完整锁文件备份。3.3 安装报错的排查口诀在评论区见过最多的报错就是这种Module build failed (from ./node_modules/sass-loader/dist/cjs.js): Error: Node Sass does not yet support your current environment: Windows 64-bit with Unsupported runtime这样的报错基本就是 node-sass 二进制匹配失败。解决办法很简单卸载node-sass改用sassnpm uninstall node-sass npm install -D sass如果你所在企业内网拉 npm 包不走海外源还可以先配一下镜像源再装npm config set registry https://registry.npmmirror.com配完以后删除package-lock.json重新安装就好了。注意镜像是为了让你安装更快它不改变包的 API 行为所以不要动任何业务代码。3.4 验证阶段写个最小 SCSS 组件跑通安装只是第一步最好直接验证到组件层。我通常是在目标项目里新建一个临时页面写一段最简单但能体现特性代码template div classsass-test{{ msg }}/div /template script setup const msg scss ok /script style langscss $color: #ff6600; .sass-test { color: $color; :hover { transform: scale(1.02); } } /style然后启动 dev server。页面显示橙色文字且字号没变形说明安装链路完全没有问题。到这步不出错后续基本畅通。4. 把 Scss 写进组件lang、Scoped 与深度选择器的爱恨情仇安装搞定之后代码层面的第一个问题是怎么写进.vue文件style langscss这行是谁都看得懂的能力开关。但真正容易出事的是它和 Vue 单文件组件scoped属性之间的相互作用。4.1 Scoped 的真实原理scoped并不是什么魔法它的底层逻辑是编译时给当前组件的所有元素加一个>style scoped .title { color: red; } /style编译后大概长这样.title[data-v-7ba5bd90] { color: red; }所以你在.title上写的 color 才会只作用于当前组件不会泄漏到全局。了解了这个机制很多匪夷所思的样式失效就都能解释了。4.2 SCSS 嵌套和 Scoped 的组合写法很多人在 SCSS 里写嵌套时喜欢把最外层类名当作“根”style langscss scoped .card { .card__title { color: #333; } .card__body { padding: 16px; } } /style这个写法本身没问题编译后会变成.card .card__title[data-v-xxx]。请注意>style langscss scoped .btn { color: red; } // 不会生效因为 .btn 在子组件内部 /style这种情况下必须用深度选择器才能穿透到子组件内部。不同时代的项目写法完全不一样场景写法Vue 2 Vue CLI 4 旧项目/deep/ .btn或::v-deep .btnVue 2 sass-loader 新版本::v-deep(.btn)Vue 3:deep(.btn)在 SCSS 嵌套里深度选择器最好配合父选择器一起写style langscss scoped .card { :deep(.btn) { color: red; } } /style这样编译出来是.card[data-v-xxx] .btn既保持了 scoped 的作用域限制又能穿透到子组件内部。如果你项目里还在用 Vue3 的::v-deep控制台会给你 warnings但通常不会直接报错还是尽早迁移到:deep()避免后续升级 Vue 版本时样式静默失效。4.4 全局样式文件和 scoped 的边界项目里总有些样式是要全局共用的重置样式、栅格系统、动画 keyframes、公共工具类。这一部分不要放在组件里加 scoped应该在main.js入口统一引入一个全局 SCSSimport /styles/index.scss在styles/index.scss里再通过use引入变量、函数、mixin、基础样式这样全局都能用和组件 scoped 样式互不打扰。这两个边界分清后你的项目样式体系就会非常干净。5. 全局变量和 Mixin 注入告别每个组件重复 import如果只在某个组件里用变量那你直接use /styles/variables.scss;就行。但业务项目里变量和 Mixin 每个组件都可能用每个文件都写一行use会让人抓狂而且如果将来迁移目录还得改几十个文件。业界标准做法是通过构建工具自动注入。5.1 Vue2 Webpack 的配置方式在项目根目录的vue.config.js里加上这段const { defineConfig } require(vue/cli-service) module.exports defineConfig({ css: { loaderOptions: { scss: { additionalData: use /styles/variables.scss as *; } } } })然后你写variables.scss时里面内容直接$primary-color: #1890ff; $success-color: #52c41a; $border-radius-base: 4px; mixin ellipsis($lines: 1) { /* ... */ }所有组件里就能直接使用$primary-color和include ellipsis(2)不需要任何 import 语句。这里有个容易踩的大坑不同sass-loader版本字段名不一样。在 sass-loader 8 及以前这个配置项叫data网上很多老教程写的prependData是 v9 过渡期的写法从 v10 开始官方推荐的是additionalData。新项目照着老教程配了一个prependData编译时要么直接报错要么静默不生效。我统一的建议是sass-loader 8 及以下datasass-loader 9建议直接升到 10 或者 11避免踩坑sass-loader 10 及以上一律写additionalData5.2 Vue3 Vite 的配置方式对应到vite.config.tsimport { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, ./src) } }, css: { preprocessorOptions: { scss: { additionalData: use /styles/variables.scss as *; } } } })注意这里的需要靠 Vite 的 alias 来解析如果你的项目没配写成相对路径也行但维护起来不方便还是建议配上。5.3 注入内容的原则变量和 Mixin 可以真实 CSS 别注入新手最容易犯的错误是把所有样式都塞进additionalData里结果每个组件的编译产物里都被插入了同样的几百行基础样式编译速度下降不说最终打包的 CSS 体积也会成倍膨胀还容易引发样式覆盖顺序的混乱。我这里有一个从实践里总结出来的原则additionalData只用来注入变量、函数、Mixin 这类“不直接产生 CSS 规则”的内容其他重置样式、公共类名走全局index.scss正常引入。换句话说这 3 类放进去安全variables变量mixins混合宏functionsSass 函数这种文件里可以包含空的选择器吗尽量避免。判断标准很简单编译这份文件如果输出了一段你不要的 CSS就不要放进additionalData。5.4 为什么我推荐用use而不是import老项目用import已经有十几年历史了但它有几个很讨厌的问题同名变量会互相覆盖、无法确定依赖关系、局部变量泄漏到全局的作用域。所以 Dart Sass 官方逐步弃用import推荐用use和forward。use的好处是每个文件的模块都有自己的命名空间默认是文件名避免变量冲突。如果嫌写着长可以用use xxx as *;把它展开放到全局。上面的additionalData配置这么写就是这种用法。如果你在项目的其他 SCSS 文件里想引用公共变量同样要写use /styles/variables.scss as *;注意文件路径不带_前缀Sass 会自动补全_variables.scss这个文件。6. 工程化主题切换变量拆分、暗黑模式与样式规范装完、跑通、全局注入做完这只是完成了 Sass 的“基础功”。一个真正能长期维护的项目还需要在样式工程化上多走几步。6.1 推荐的项目样式目录结构我比较喜欢这样的src/styles组织方式src/styles/ ├── _variables.scss // 设计变量颜色、间距、字号、圆角 ├── _mixins.scss // 混合宏 ├── _functions.scss // 函数 ├── index.scss // 全局样式入口引入基础样式 └── themes/ ├── light.scss └── dark.scss把所有设计 token 集中在一个文件里是让 UI 风格一致的基础。产品经理过来说“主色调换成蓝色”你只需要改$primary-color一个变量全站焕然一新。当然了前提是组件里不能到处硬编码颜色值这个要靠代码 review 习惯去纠偏。6.2 Sass 变量和 CSS 变量组合实现暗黑模式做暗黑模式时最常见的思路是“切 class 或切>$themes: ( light: ( bg-color: #ffffff, text-color: #333333, primary-color: #1890ff ), dark: ( bg-color: #141414, text-color: rgba(255, 255, 255, 0.85), primary-color: #4096ff ) );然后在入口文件里通过each循环生成主题块each $theme-name, $theme-map in $themes { :root[data-theme#{$theme-name}] { each $key, $value in $theme-map { --#{$key}: #{$value}; } } }这样生成的 CSS 里就有:root[data-themelight] { --bg-color: #ffffff; --text-color: #333333; ... } :root[data-themedark] { --bg-color: #141414; --text-color: rgba(255,255,255,.85); ... }组件里直接用var(--bg-color)就能做到运行时无缝切换主题。Sass 负责编译期的计算和循环CSS 变量负责运行时的动态刷新两者互补配合得刚刚好。6.3 给样式加一道规范护栏Stylelint随着项目变大光靠人肉检查这也不够那也不够。我强烈建议项目里引入 Stylelint 配合 stylelint-scss 插件它能把 Sass 的写法也纳入检查范围。安装的核心依赖npm install -D stylelint stylelint-config-standard-scss stylelint-scss.stylelintrc里做如下简单配置{ extends: [stylelint-config-standard-scss], rules: { scss/dollar-variable-pattern: ^[a-z][a-zA-Z0-9-]$, color-no-invalid-hex: true, declaration-block-trailing-semicolon: always } }这些规则会自动提示你变量命名不符合规范、颜色值不合法等问题。我实际跑下来最大的感受是它能把团队里十种不同的写法逐步收敛成一种。尤其是新人加入以后至少样式层面有机器把关省去不少互相挑毛病的精力。6.4 后期维护的一点小体会踩过这么多坑之后我自己的体会是Sass 出问题的概率其实不高绝大多数问题都出在“环境版本不匹配”和“旧教程误导”上。所以无论你是接手别人的老项目还是自己从零搭建请一定先确认版本矩阵再谈功能和配置。遇到样式不生效先打开浏览器的 Elements 面板看编译后的选择器到底长什么样很多时候答案就在那几行代码里而不是盲目去改配置。如果你能把前面讲的变量集中管理、Mixin 复用、全局注入原则、主题切换机制都落地到项目里SCSS 就不只是个“写嵌套样式”的工具而是一套真正能支撑大型项目长期演化的样式体系。后续如果要换肤、改设计规范或者做组件库迁移撑场面的还是这些基础工作。
返回列表