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

时间:2022-10-19 06:57:08

In an application I'm working, I have to dynamically compile SASS before rendering on the client (caching system is coming, don't worry). Currently I'm using node-sass and everything is working great.

在我正在使用的应用程序中,我必须在客户端上呈现之前动态编译SASS(缓存系统即将到来,不用担心)。目前我正在使用node-sass,一切都很好。

Here is what I'm working on so far. Other project-specific code has been removed for brevity:

这是我到目前为止所做的工作。为简洁起见,已删除其他特定于项目的代码:

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;
}

The wrinkle is that now I need to pass in dynamic data from Node and have that compile like a normal SASS variable. Initially I tried using PostCSS, because I noticed that variable injection was something it could do. Unfortunately, that didn't work. PostCSS kicks in after the compilation phase, which fails miserably by this point.

皱纹是现在我需要从Node传递动态数据并将其编译为普通的SASS变量。最初我尝试使用PostCSS,因为我注意到它可以做变量注入。不幸的是,这不起作用。 PostCSS在编译阶段开始,在这一点上惨败。

Next, I tried to use underscore templates to try and overwrite using node-sass' importer():

接下来,我尝试使用下划线模板尝试使用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' })
            };
        }
    });

Which resulted in the following error:

这导致以下错误:

Error: error reading values after :

Obviously SASS didn't like underscore's variable syntax..

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


TL;DR

How can I pass dynamic variables to SASS from within my Node application?

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


Additional Information

  1. My team and I are not completely adverse to switching to something like Stylus; however, we have made significant progress so far and it would be a pain.
  2. 我和我的团队并不完全不喜欢切换到Stylus这样的东西;然而,到目前为止,我们取得了重大进展,这将是一个痛苦。

3 个解决方案

#1


54  

I found myself in a very similar situation. We had a lot of existing SASS that now needed to accept dynamic values/variables to be used throughout (as variables). I originally went down the route of writing temporary directories/files and essentially creating a "proxy entry point" which would create a proxy_entry.scss and variables.scss and bootstrap the actual entry.scss with intended SASS variables declared. This worked fine and achieved the desired results, but it felt a bit overcomplicated...

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

It turns out there is a much simpler solution available thanks to node-sass's options.data option. This accepts a "SASS string to be evaluated".

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

Type: String Default: null Special: file or data must be specified

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

A string to pass to libsass to render. It is recommended that you use includePaths in conjunction with this so that libsass can find files when using the @import directive.

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

This completely eliminated the need for writing/managing all of the temporary directories and files.

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

Visual TL;DR

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

The solution boils down to something like this

解决方案归结为类似的东西

1.) Define sassOptions as usual

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

2.) Write the "dynamic SASS string" for options.data

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

3.) Evaluate the SASS as usual

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

Note: this is assuming your entry.scss imports some variables.scss that defines variables as "defaults".

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

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

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

Piecing it all together as an example

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);

Where the "sassGenerator" functions could looks something like

“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 + "';";
}

This enables you to write your SASS just as you did before, using SASS variables anywhere that they are needed. It also doesn't tie you down to any "special dynamic sass implementation" (i.e. this avoids using "underscore/lodash templating throughout your .scss files). It also means you can take advantage of IDE features, linting, etc... just the same since you are now just back to writing regular SASS.

这使您能够像以前一样使用SASS变量编写SASS,并在任何需要的位置使用SASS变量。它也不会将你绑定到任何“特殊的动态sass实现”(即这避免在整个.scss文件中使用“下划线/ lodash模板”。)这也意味着你可以利用IDE功能,linting等...因为你现在回到正常的SASS写作,所以一样。

Additionally, it translates nicely to non-node/http/compile-on-the-fly usages such as pre-compiling multiple variations of entry.scss given multiple value sets via Gulp, etc...

此外,它很好地转换为非节点/ http /即时编译用法,例如预先编译entry.scss的多个变体,通过Gulp等给出多个值集...

I hope this helps you @ChrisWright (and others) out! I know I struggled finding information on the subject and I imagine this is a fairly common use-case (wanting to pass dynamic values into SASS from a Database, config, HTTP parameters, etc...).

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

#2


2  

I was able to solve this after I wrapped my head around node-sass' importer() method. My solution involved underscore templates and manually reading files as they come in. It's not the most elegant or efficient solution, but it is only run once per generated page. After that, files are minified and cached for future requests.

在我绕过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 })
        });
    });
}

Using this solution, we are able to reference normal underscore variable syntax inside our SCSS partials. As an example:

使用此解决方案,我们能够在SCSS部分内部引用正常的下划线变量语法。举个例子:

body {
    color: <%= data.colour %>;
}

#3


0  

I was solving a similar problem though not in Node but in Java. I was required to render SASS variables from database to generate website theme, based on client that is accessing the website.

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

I explored some solutions and came across this third party service https://www.grooveui.com. It offers a language agnostic solution to solve this problem.

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

#1


54  

I found myself in a very similar situation. We had a lot of existing SASS that now needed to accept dynamic values/variables to be used throughout (as variables). I originally went down the route of writing temporary directories/files and essentially creating a "proxy entry point" which would create a proxy_entry.scss and variables.scss and bootstrap the actual entry.scss with intended SASS variables declared. This worked fine and achieved the desired results, but it felt a bit overcomplicated...

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

It turns out there is a much simpler solution available thanks to node-sass's options.data option. This accepts a "SASS string to be evaluated".

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

Type: String Default: null Special: file or data must be specified

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

A string to pass to libsass to render. It is recommended that you use includePaths in conjunction with this so that libsass can find files when using the @import directive.

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

This completely eliminated the need for writing/managing all of the temporary directories and files.

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

Visual TL;DR

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

The solution boils down to something like this

解决方案归结为类似的东西

1.) Define sassOptions as usual

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

2.) Write the "dynamic SASS string" for options.data

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

3.) Evaluate the SASS as usual

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

Note: this is assuming your entry.scss imports some variables.scss that defines variables as "defaults".

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

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

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

Piecing it all together as an example

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);

Where the "sassGenerator" functions could looks something like

“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 + "';";
}

This enables you to write your SASS just as you did before, using SASS variables anywhere that they are needed. It also doesn't tie you down to any "special dynamic sass implementation" (i.e. this avoids using "underscore/lodash templating throughout your .scss files). It also means you can take advantage of IDE features, linting, etc... just the same since you are now just back to writing regular SASS.

这使您能够像以前一样使用SASS变量编写SASS,并在任何需要的位置使用SASS变量。它也不会将你绑定到任何“特殊的动态sass实现”(即这避免在整个.scss文件中使用“下划线/ lodash模板”。)这也意味着你可以利用IDE功能,linting等...因为你现在回到正常的SASS写作,所以一样。

Additionally, it translates nicely to non-node/http/compile-on-the-fly usages such as pre-compiling multiple variations of entry.scss given multiple value sets via Gulp, etc...

此外,它很好地转换为非节点/ http /即时编译用法,例如预先编译entry.scss的多个变体,通过Gulp等给出多个值集...

I hope this helps you @ChrisWright (and others) out! I know I struggled finding information on the subject and I imagine this is a fairly common use-case (wanting to pass dynamic values into SASS from a Database, config, HTTP parameters, etc...).

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

#2


2  

I was able to solve this after I wrapped my head around node-sass' importer() method. My solution involved underscore templates and manually reading files as they come in. It's not the most elegant or efficient solution, but it is only run once per generated page. After that, files are minified and cached for future requests.

在我绕过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 })
        });
    });
}

Using this solution, we are able to reference normal underscore variable syntax inside our SCSS partials. As an example:

使用此解决方案,我们能够在SCSS部分内部引用正常的下划线变量语法。举个例子:

body {
    color: <%= data.colour %>;
}

#3


0  

I was solving a similar problem though not in Node but in Java. I was required to render SASS variables from database to generate website theme, based on client that is accessing the website.

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

I explored some solutions and came across this third party service https://www.grooveui.com. It offers a language agnostic solution to solve this problem.

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