27

在我正在工作的应用程序中,我必须在客户端渲染之前动态编译 SASS(缓存系统来了,不用担心)。目前我正在使用node-sass,一切都很好。

这是我目前正在做的事情。为简洁起见,已删除其他特定于项目的代码:

var sass            = require('node-sass'),
    autoprefixer    = require('autoprefixer-core'),
    vars            = require('postcss-simple-vars'),
    postcss         = require('postcss'),

function compileCSS() {
    var result = sass.renderSync({
            file: 'path/to/style.scss'
        });

    return postcss([autoprefixer]).process(result.css.toString()).css;
}

问题是现在我需要从 Node 传递动态数据并像普通的 SASS 变量一样编译。最初我尝试使用PostCSS,因为我注意到变量注入是它可以做的事情。不幸的是,那没有用。PostCSS 在编译阶段之后启动,此时编译阶段失败了。

接下来,我尝试使用下划线模板尝试使用 node-sass' 覆盖importer()

var result = sass.renderSync({
        file: 'path/to/style.scss',
        importer: function(url, prev, done) {
            var content = fs.readFileSync(partial_path),
                partial = _.template(content.toString());

            return {
                contents: partial({ test: 'test' })
            };
        }
    });

这导致了以下错误:

Error: error reading values after :

显然 SASS 不喜欢下划线的变量语法..


TL;博士

如何从我的 Node 应用程序中将动态变量传递给 SASS?


附加信息

  1. 我和我的团队并不完全反对改用Stylus 之类的东西;但是,到目前为止,我们已经取得了重大进展,这将是一种痛苦。
4

3 回答 3

66

我发现自己处于非常相似的境地。我们有很多现有的 SASS,现在需要接受要在整个过程中使用的动态值/变量(作为变量)。我最初走的是编写临时目录/文件的路线,本质上是创建一个“代理入口点”,它将创建一个proxy_entry.scssvariables.scss引导实际entry.scss声明的预期 SASS 变量。这工作正常并达到了预期的结果,但感觉有点过于复杂......

事实证明,由于node-sass 的options.data选项,有一个更简单的解决方案可用。这接受“要评估的 SASS 字符串”。

类型:字符串 默认值:null 特殊:必须指定文件或数据

传递给 libsass 进行渲染的字符串。建议您将 includePaths 与此结合使用,以便 libsass 在使用 @import 指令时可以找到文件。

这完全消除了编写/管理所有临时目录和文件的需要。

视觉 TL;DR

SASS 中的动态变量与 node-sass

解决方案归结为这样

1.) 像往常一样定义 sassOptions

var sassOptionsDefaults = {
  includePaths: [
    'some/include/path'
  ],
  outputStyle:  'compressed'
};

2.) 为options.data

var dataString =
  sassGenerator.sassVariables(variables) +
  sassGenerator.sassImport(scssEntry);
var sassOptions = _.assign({}, sassOptionsDefaults, {
  data: dataString
});

3.) 像往常一样评估 SASS

var sass = require('node-sass');
sass.render(sassOptions, function (err, result) {
  return (err)
    ? handleError(err);
    : handleSuccess(result.css.toString());
});

注意: 这是假设您的entry.scss导入一些variables.scss将变量定义为“默认值”。

// variables.scss
$someColor: blue !default;
$someFontSize: 13px !default;

// entry.scss
@import 'variables';
.some-selector { 
  color: $someColor;
  font-size: $someFontSize;
}

将它们拼凑在一起作为示例

var sass = require('node-sass');

// 1.) Define sassOptions as usual
var sassOptionsDefaults = {
  includePaths: [
    'some/include/path'
  ],
  outputStyle:  'compressed'
};

function dynamicSass(scssEntry, variables, handleSuccess, handleError) {
  // 2.) Dynamically create "SASS variable declarations"
  // then import the "actual entry.scss file".
  // dataString is just "SASS" to be evaluated before
  // the actual entry.scss is imported.
  var dataString =
    sassGenerator.sassVariables(variables) +
    sassGenerator.sassImport(scssEntry);
  var sassOptions = _.assign({}, sassOptionsDefaults, {
    data: dataString
  });

  // 3.) render sass as usual
  sass.render(sassOptions, function (err, result) {
    return (err)
      ? handleError(err);
      : handleSuccess(result.css.toString());
  });
}

// Example usage.
dynamicSass('some/path/entry.scss', {
  'someColor': 'red',
  'someFontSize': '18px'
}, someSuccessFn, someErrorFn);

“sassGenerator”函数看起来像

function sassVariable(name, value) {
  return "$" + name + ": " + value + ";";
}

function sassVariables(variablesObj) {
  return Object.keys(variablesObj).map(function (name) {
    return sassVariable(name, variablesObj[name]);
  }).join('\n')
}

function sassImport(path) {
  return "@import '" + path + "';";
}

这使您可以像以前一样编写 SASS,在需要的任何地方使用 SASS 变量。它也不会将您束缚于任何“特殊的动态 sass 实现”(即这避免了在整个.scss文件中使用“下划线/lodash 模板”)。这也意味着您可以利用 IDE 功能、linting 等......只是相同,因为您现在刚刚回到编写常规 SASS

此外,它可以很好地转换为非节点/http/compile-on-the-fly 用法,例如entry.scss通过 Gulp 预编译给定多个值集的多个变体等...

我希望这可以帮助您@ChrisWright(和其他人)!我知道我很难找到有关该主题的信息,我想这是一个相当常见的用例(希望将动态值从数据库、配置、HTTP 参数等传递到 SASS 中……)。

于 2015-07-27T15:00:34.957 回答
2

在我用 node-sass 的importer()方法来解决这个问题之后,我能够解决这个问题。我的解决方案涉及下划线模板和在文件进入时手动读取文件。这不是最优雅或最有效的解决方案,但每个生成的页面只运行一次。之后,文件被缩小并缓存以供将来的请求使用。

// Other none-sass render parameters omitted for brevity
importer: function (url, prev, done) {

    // Manually read each partial file as it's encountered
    fs.readFile(url, function (err, result) {
        if (err) {

            // If there is an error reading the file, call done() with
            // no content to avoid a crash
            return done({
                contents: ''
            });
        }

        // Create an underscore template out of the partial
        var partial = _.template(result.toString());

        // Compile template and return its contents to node-sass for
        // compilation with the rest of the SCSS partials
        done({
            contents: partial({ data: YOUR_DATA_OBJECT })
        });
    });
}

使用这个解决方案,我们可以在 SCSS 部分中引用正常的下划线变量语法。举个例子:

body {
    color: <%= data.colour %>;
}
于 2015-07-25T01:08:31.010 回答
-1

我正在解决类似的问题,但不是在 Node 中,而是在 Java 中。我需要根据正在访问网站的客户端从数据库中渲染 SASS 变量以生成网站主题。

我探索了一些解决方案并遇到了这个第三方服务https://www.grooveui.com。它提供了一个与语言无关的解决方案来解决这个问题。

于 2017-08-02T17:57:23.397 回答