Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting variables during SASS compilation with Node

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.
like image 730
Chris Wright Avatar asked Jun 30 '15 21:06

Chris Wright


People also ask

How do you use variables in Sass?

The basic syntax for defining a variable is simple: Just use a $ before the variable name and treat its definition like a CSS rule: Sass Variable Syntax: $<variable name>:<value>; The following declares a variable named large-font.

How does node work in Sass?

Node-sass is a library that provides binding for Node. js to LibSass, the C version of the popular stylesheet preprocessor, Sass. It allows you to natively compile . scss files to css at incredible speed and automatically via a connect middleware.

What is the difference between node-Sass and Sass?

node-sass and Sass can be categorized as "CSS Pre-processors / Extensions" tools. node-sass and Sass are both open source tools. It seems that Sass with 12K GitHub stars and 1.93K forks on GitHub has more adoption than node-sass with 6.49K GitHub stars and 949 GitHub forks.


Video Answer


1 Answers

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

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

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

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.

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

Visual TL;DR

Dynamic Variables in SASS with 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".

// 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

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.

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

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

like image 58
Erik Aybar Avatar answered Sep 29 '22 10:09

Erik Aybar