在使用Node进行SASS编译期间注入变量

时间:2015-06-30 21:26:30

标签: css node.js express sass node-sass

在我正在工作的应用程序中,我必须在客户端呈现之前动态编译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,因为我注意到变量注入是something it could do。不幸的是,这没有用。 PostCSS在编译阶段开始,在这一点上惨遭失败。

接下来,我尝试使用underscore模板尝试使用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; DR

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

其他信息

  1. 我和我的团队并不完全不喜欢切换到Stylus之类的东西;然而,到目前为止,我们取得了重大进展,这将是一种痛苦。

3 个答案:

答案 0 :(得分:58)

我发现自己情况非常相似。我们有很多现有的SASS,现在需要接受要在整个过程中使用的动态值/变量(作为变量)。我最初沿着编写临时目录/文件的路线走下去,基本上创建了一个"代理入口点"这将创建一个proxy_entry.scssvariables.scss并使用声明的SASS变量引导实际的entry.scss这很好,并取得了预期的效果,但感觉有点过于复杂......

由于node-sass's options.data选项,可以使用更简单的解决方案。这接受一个" SASS字符串进行评估"。

  

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

     

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

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

Visual TL; DR

Dynamic Variables in SASS with node-sass

解决方案归结为类似

1.)像往常一样定义sassOptions

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

2.)写下"动态SASS字符串"对于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 /即时编译用法,例如通过Gulp预先编译entry.scss多个变量给定多个值集......

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

答案 1 :(得分: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 %>;
}

答案 2 :(得分:0)

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

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