基于环境的Sass动态导入

问题描述

我正在尝试根据环境切换sass导入URL。我已经审查了许多其他问题和答案,这一直困扰着我。 @import不允许使用动态字符串,因此我尝试根据环境变量对每个字符串和@ if / @ else使用mixin,但是我无法从其他sass文件访问根环境变量。

webpack.config.js

const scssRule = config.module.rules.find(rule => rule.test.test(".scss"));
const scssLoaderConfig = scssRule.use.find(({loader}) => loader === "sass-loader");
scssLoaderConfig.options = scssLoaderConfig.options || {};
scssLoaderConfig.options["implementation"] = require("sass"); //dart-sass
scssLoaderConfig.options["additionalData"] = `$env:"${env}";`; //Pass env to sass

theme.scss

//NOTE: This $env line is not actually in the file. It gets injected by webpack via sass-loader additionalData
$env: "preprod";

@forward "icons" as icon_*;
//More @forward directives such as colors

_icons.scss

@mixin importIconspreprod {
    @import "https://preprod.example.com/icons.css";
}
@mixin importIconsprod {
    @import "https://example.com/icons.css";
}

@mixin importIcons() {
    //SassError: Undefined variable.
    @if $env == "preprod" {
        @include importIconspreprod;
    } @else {
        @include importIconsprod;
    }
    
    //More styles here
}

所需的实现

@use "theme-package" as theme;

//Import icons from theme.icon namespace
@include theme.icon_importIcons;

解决方法

一段时间后,我找到了解决方案。我最终生成了_env.scss文件作为webpack的一部分。此文件包含指示环境的$ env sass变量,并由_icons.scss加载。下面的代码:

webpack.config.js

const writeEnvScss = require("./scripts/write-env-scss.js");

//Write _env.scss,default to "prod"
writeEnvScss("./packages/Theme/src/Component/includes/_env.scss","prod");

_env.scss作为构建的一部分生成的

$env:"preprod";

_icons.scss

//"env" file is created as part of webpack.config.js
@use "includes/env" as *;

@mixin importIconsPreprod {
    @import "https://preprod.example.com/icons.css";
}
@mixin importIconsProd {
    @import "https://example.com/icons.css";
}

@mixin importIcons() {
    @if $env == "preprod" {
        @include importIconsPreprod;
    } @else {
        @include importIconsProd;
    }
}

write-env-scss.js

const fs = require("fs");
const path = require("path");

//looks for optional --env cli param or NODE_ENV environment variable
module.exports = function(scssPath,envDefault) {
    scssPath = scssPath || null;
    if (!scssPath) {
        throw new Error("scss path required");
    }
    envDefault = envDefault || "local";

    //Determine env
    let env = getArg("--env") || process.env.NODE_ENV || null;
    switch (env) {
        case "development":
        case "local":
            env = "local";
            break;
        case "preprod":
            break;
        case "production":
        case "prod":
            env = "prod";
            break;
        default:
            env = envDefault;
            break;
    }
    console.log("ENV =>",env);

    //Write _env.scss to Theme
    fs.writeFileSync(path.resolve(scssPath),`$env:"${env}";`);
};

function getArg(key) {
    var index = process.argv.indexOf(key);
    var next = process.argv[index + 1];
    return index < 0 ? null : !next || next[0] === "-" ? true : next;
}
,

在我的项目(跨浏览器扩展)中,我正在使用node-sass config中的includePaths选项来实现类似的目的。

通常,includePaths扩展了可以导入scss模块以正确编译的文件夹范围。由于它是js API,因此您可以通过变量动态设置此路径。

这是我的操作方式示例(我使用gulpgulp-sass,但它与webpack的工作方式相同。示例得到了简化,并且没有节点的“路径”):

  • 文件夹结构:
styles
|-- firefox
|   |-- _vars.scss
|
|-- chrome
|   |-- _vars.scss
|
|-- styles.scss // main file
|-- gulpfile.js // builder
|-- build
  • styles.scss
@import "vars";
  • gulpfile.js
const { src,dest } = require('gulp');
const sass = require('gulp-sass');

function styles(browser){
  const srcPath = "*.scss";
  const destPath = "build";

  return src(srcPath)
           .pipe(sass({ includePaths: browser }))
           .pipe(dest(destPath))
}

gulp.default = function(){
  const varDirname = "firefox"; // or "chrome"

  styles(varDirname);
}

请注意,在includePaths中仅包含文件所在的目录名称,而不要像browser/*.scss这样的全局名称,这一点很重要。