在 Sass 中处理环境变量时,真正令人困扰的往往并非逻辑判断本身,而是变量未能成功传递进入编译流程,或者虽然传递成功但数据类型不匹配。这类问题一旦出现,排查过程相当耗时,且故障发生的场景通常非常典型且固定。

为什么 $env 在 Sass 里总是 undefined
问题的根源在于:环境变量根本没有被注入到 Sass 的编译流程之中。无论你使用的是 Webpack、Vite 还是直接通过 CLI 进行编译,在默认情况下,Node.js 中的 process.env.NODE_ENV 并不会自动出现在 Sass 的作用域内。当你编写 @if $env == "production" 时,$env 实际上从未被声明过,Sass 编译器自然会抛出 Undefined variable 错误。
常见的失败场景通常包括以下几种:
- 在
webpack.utils.js中通过define: { 'process.env.ENV': JSON.stringify('dev') }进行配置,期望 Sass 能直接读取——但 Sass 并不识别 JavaScript 对象,这条路根本行不通。 - 直接使用
sass --load-path=src main.scss进行编译,却遗漏了--define=env=dev参数,结果等于做了无用功。 - 在 Vite 中花费大量精力配置
define,却忘记同步设置css.preprocessorOptions.sass.define,导致变量仍然无法传递。
实际开发中如何解决?核心思路其实非常清晰:显式传递。
- 使用 CLI 编译时直接添加参数:
sass --define=env="production" src/main.scss dist/main.css。 - Webpack 配合 sass-loader 使用时,可以在
options.additionalData中注入变量,例如additionalData: '$env: "' + process.env.NODE_ENV + '";'。 - Vite 项目中,在
vite.config.ts的css.preprocessorOptions.sass.define配置下写入{ env: JSON.stringify(process.env.NODE_ENV) }。 - 有一个非常实用的小技巧值得养成习惯:在
@if判断之前先用@debug $env;输出变量值,确认变量已经存在并且类型符合预期。
@if $env == "production" 为什么没进分支
变量已经正确注入之后,条件判断仍然失效,这又是什么原因?问题通常出在变量值本身。Sass 对字符串的比较要求极其严格——末尾多一个空格、大小写不一致、甚至用单引号包裹变量名,都会导致条件判断不成立。
这里的陷阱其实并不难避免:
- 务必使用双引号来包裹字符串:
@if $env == "production"是正确的写法,而@if $env == production则是错误的,因为后者会把production当作一个未定义的变量来处理。 - 使用
type-of($env)确认变量类型,确保返回的是string,而不是null或bool。 - 如果
$env来自@import 'config',那么必须确保config.scss在条件语句之前已经被正确引入。 - 在编写
@else if $env == "development"之后,别忘了补充一个@else兜底分支。否则,一旦测试环境中没有定义该变量,对应的样式就会直接丢失。
开发环境样式覆盖生产环境的坑怎么避
变量对了,值也正确;分支写了,覆盖也做了防范——可上线之后,最终展现的样式仍然是混乱的。这个问题多半出在编译顺序上。多个 @if 分支生成的选择器权重是相同的,后编译的规则会覆盖前面的规则。例如开发环境的样式写在 SCSS 文件底部,线上构建时它反而生效,最终导致线上出现样式缺陷。
解决方案其实并不复杂:
- 将环境相关的样式单独抽取为独立文件,按照明确的顺序进行
@import,例如@import "env/base"; @import "env/production"; @import "env/development";。这样编译顺序就完全由你掌控。 - 使用更具体的选择器来提升权重。比如生产环境添加一个
.env--prod类,开发环境使用.env--dev,再配合属性选择器编写[data-env="production"] .btn,这样就不必担心顺序问题了。 - 开发专用的样式,比如调试相关的规则,只在
@if $env == "development"分支中输出,并且确保这些样式不包含任何线上逻辑。 - CI 构建时加上
--no-source-map和--style=compressed参数,避免开发阶段的注释或冗余规则混入最终的生产 CSS 文件中。
如何安全地在 Sass 里读取构建时的环境标志
最稳妥的思路是:不要试图在 Sass 中直接读取 process.env、import.meta.env 或 __DEV__ 这类运行时标识——Sass 编译器完全不识别这些内容。唯一可靠的方式,就是通过构建工具将静态字符串明明白白地传递进去。
几个具体建议:
- 在 Webpack 中,与其使用
DefinePlugin将process.env注入到 Sass,不如改用sass-resources-loader或additionalData来直接注入变量,路径更短、问题更少。 - Vite 中的
define配置只能接受字面量字符串、数字或布尔值。不要尝试编写__BUILD_TIME__: new Date()这样的代码,Sass 无法解析这种动态值。 - 如果需要区分 CI 和本地构建,可以在 CI 脚本中传入不同的
--define参数,例如--define=build_source="ci",本地则使用--define=build_source="local"。 - 所有环境变量名统一加上前缀,比如
$build_env、$build_source,避免与项目中的其他变量产生命名冲突。
有一个细节特别容易被忽略:Sass 编译完成之后,@if 的结构完全不会保留。如果当初没有在变量声明的地方添加注释,半年之后再回来维护,恐怕谁也记不清 $build_env 究竟从何而来、有哪些合法取值。因此,养成好习惯——在注入变量的地方写清楚来源和取值范围,这是对未来的自己负责。
