27

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.

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.

Next, I tried to use underscore templates to try and overwrite using 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..


TL;DR

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


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.
4

3 に答える 3

66

私は非常によく似た状況にいることに気づきました。全体で (変数として) 使用される動的な値/変数を受け入れる必要がある既存の SASS がたくさんありました。私は当初、一時ディレクトリ/ファイルを作成し、本質的に「プロキシエントリポイント」を作成するというルートをproxy_entry.scssたどりvariables.scssましentry.scssた。これはうまく機能し、望ましい結果を達成しましたが、少し複雑すぎると感じました...

node-sass のoptions.dataオプションのおかげで、はるかに単純なソリューションが利用可能であることがわかりました。これは、「評価されるSASS文字列」を受け入れます。

タイプ: 文字列 デフォルト: null 特殊: ファイルまたはデータを指定する必要があります

レンダリングするために libsass に渡す文字列。@import ディレクティブを使用するときに libsass がファイルを検索できるように、これと一緒に includePaths を使用することをお勧めします。

これにより、すべての一時ディレクトリとファイルを書き込み/管理する必要が完全になくなりました。

ビジュアル TL;DR

node-sass を使用した 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 実装」に縛られることもありません (つまり、これにより、ファイル全体で「アンダースコア/lodash テンプレートの使用が回避され.scssます)。また、IDE 機能、linting などを利用できることも意味します。通常の SASS の作成に戻ったばかりなので、同じです。

entry.scssさらに、指定された複数の値セットの複数のバリエーションを Gulp などを介してプリコンパイルするなど、非ノード/http/コンパイルオンザフライの使用法にも適切に変換されます。

これが@ChrisWright(および他の人)の助けになることを願っています!この件に関する情報を見つけるのに苦労したことは知っていますが、これはかなり一般的なユースケースだと思います(データベース、構成、HTTPパラメーターなどから動的な値をSASSに渡したい...)。

于 2015-07-27T15:00:34.957 に答える
2

node-sassのimporter()メソッドに頭を悩ませた後、これを解決できました。私のソリューションには、アンダースコア テンプレートと、ファイルが入ってくると手動での読み取りが含まれていました。これは、最も洗練された効率的なソリューションではありませんが、生成されたページごとに 1 回だけ実行されます。その後、ファイルは縮小され、将来のリクエストのためにキャッシュされます。

// 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 に答える