Подтвердить что ты не робот

Инъекционные переменные во время компиляции SASS с помощью Node

В приложении, в котором я работаю, мне приходится динамически компилировать 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 запускается после фазы компиляции, которая с этой точки зрения терпит неудачу.

Затем я попытался использовать 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

Как передать динамические переменные в SASS из моего приложения Node?


Дополнительная информация

  • Моя команда и я не совсем противно переходить на нечто вроде Stylus; однако до сих пор мы достигли значительного прогресса, и было бы больно.
4b9b3361

Ответ 1

Я оказался в очень похожей ситуации. У нас было много существующих SASS, которые теперь должны были принимать динамические значения/переменные, которые будут использоваться повсюду (в качестве переменных). Сначала я пошел по пути записи временных каталогов/файлов и по существу создавал "точку входа прокси", которая создавала бы proxy_entry.scss и variables.scss и загружала фактический entry.scss с предполагаемыми переменными SASS. Это отлично работало и добилось желаемых результатов, но было немного сложнее...

Оказывается, доступно гораздо более простое решение благодаря опции node -sass options.data. Это принимает строку SASS, подлежащую оценке.

Тип: String Значение по умолчанию: null Специально: файл или данные должны быть указаны

Строка для передачи в libsass для рендеринга. Рекомендуется использовать includePaths в сочетании с этим, чтобы libsass мог находить файлы при использовании директивы @import.

Это полностью устранило необходимость записи/управления всеми временными каталогами и файлами.

Visual TL; DR

Динамические переменные в SASS с  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.) Оцените САСС как обычно

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" (т.е. Это позволяет избежать использования шаблонов underscore/lodash во всех ваших файлах .scss). Это также означает, что вы можете использовать функции IDE, листинг и т.д. то же самое, так как теперь вы просто вернулись к написанию обычного SASS.

Кроме того, он отлично переносится на не-w632 > /http/компиляция на лету, такие как предварительная компиляция нескольких вариаций entry.scss с учетом множества наборов значений через Gulp и т.д.

Я надеюсь, что это поможет вам @ChrisWright (и другим)! Я знаю, что я пытался найти информацию по этому вопросу, и я думаю, что это довольно распространенный случай использования (желающий передать динамические значения в SASS из базы данных, конфигурации, параметров HTTP и т.д.).

Ответ 2

Я смог решить это после того, как я обернул голову вокруг метода node -sass 'importer(). Мое решение включало шаблоны подчеркивания и ручное чтение файлов по мере их поступления. Это не самое элегантное или эффективное решение, но оно выполняется только один раз на сгенерированную страницу. После этого файлы будут уменьшены и кэшированы для будущих запросов.

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

    // Manually read each partial file as it 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 %>;
}

Ответ 3

Я решал аналогичную проблему, хотя не в Node, а в Java. Мне нужно было отображать переменные SASS из базы данных для создания темы сайта на основе клиента, который обращается к веб-сайту.

Я изучил некоторые решения и наткнулся на эту стороннюю службу https://www.grooveui.com. Он предлагает языковое агностическое решение для решения этой проблемы.