Utilizing Node.js to dynamically inject variables in SASS compilation

I am currently working on an application where I need to dynamically compile SASS before rendering it on the client side (a caching system is in the works, so no worries there). At the moment, my tool of choice is node-sass and so far, everything is running smoothly.

This is a snippet of what I have been working on. For the sake of brevity, I have removed other project-specific code:

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 challenge now is that I need to inject dynamic data from Node and have it compile as a regular SASS variable. Initially, I attempted using PostCSS, as it seemed capable of handling variable injection according to its documentation found here. Unfortunately, this approach did not work as PostCSS operates after the compilation stage, which causes issues at this point.

Subsequently, I experimented with utilizing underscore templates to try and override the default behavior by 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' })
            };
        }
    });

However, this approach returned the following error message:

Error: error reading values after :

Evidently, SASS was not compatible with underscore's variable syntax.


TL;DR

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


Additional Information

  1. My team and I are considering transitioning to something like Stylus; however, we have made substantial progress with our current setup and switching would be quite inconvenient at this stage.

Answer №1

I encountered a situation that was quite similar to yours. We had a significant amount of existing SASS code that needed to be updated to incorporate dynamic values/variables for use throughout the stylesheets. Initially, I took the approach of creating temporary directories and files to serve as a "proxy entry point." This involved generating a proxy_entry.scss, variables.scss, and bootstrapping the actual entry.scss with the necessary SASS variables. While this method yielded the desired results, it felt somewhat convoluted...

Fortunately, there is a simpler solution available thanks to the node-sass options.data feature. This option allows for the evaluation of a "SASS string."

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

A string that is passed to libsass for rendering. It is recommended to use includePaths in conjunction with this so that libsass can locate files when using the @import directive.

This approach completely eliminates the need for managing temporary directories and files.

Visual TL;DR

https://i.stack.imgur.com/G0Hpy.png

The basic concept boils down to:

1.) Define sassOptions as usual

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

2.) Construct 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 like usual

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

Note: This assumes that your entry.scss includes a variables.scss file that defines variables as "defaults."

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

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

Putting it all together with 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.) Create "SASS variable declarations" dynamically
  // then import the actual entry.scss file.
  // dataString represents the SASS to evaluate before
  // importing the entry.scss.
  var dataString =
    sassGenerator.sassVariables(variables) +
    sassGenerator.sassImport(scssEntry);
  var sassOptions = _.assign({}, sassOptionsDefaults, {
    data: dataString
  });

  // 3.) Render sass as normal
  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);

Functions such as "sassGenerator" could resemble:

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 method allows you to continue writing your SASS as before, utilizing SASS variables wherever they are needed. It also does not restrict you to any specific dynamic SASS implementation (e.g., avoiding the use of underscore/lodash templating within your .scss files). Moreover, it enables you to leverage IDE features, linting, etc., just as before because you are once again writing standard SASS.

Furthermore, it seamlessly transitions to non-node/http/compile-on-the-fly scenarios, such as pre-compiling multiple versions of entry.scss with different sets of values via Gulp, among others.

I trust that this guidance will be beneficial to you, @ChrisWright, and others facing similar challenges! Finding information on this topic was tough for me, and I believe this use case is quite common (needing to inject dynamic values into SASS from a Database, configuration, HTTP parameters, etc.).

Answer №2

My breakthrough came when I delved into the importer() method of node-sass. To solve my problem, I utilized underscore templates and manually processed files as they were loaded. Although not the most elegant or efficient approach, this solution is only executed once for each page generation. Subsequently, the files are compressed and stored in cache for future access.

// Other render parameters excluded for simplicity
importer: function (url, prev, done) {

    // Read each partial file manually as it's encountered
    fs.readFile(url, function (err, result) {
        if (err) {

            // In case of read error, prevent a crash by calling done() with no content
            return done({
                contents: ''
            });
        }

        // Convert the partial into an underscore template
        var partial = _.template(result.toString());

        // Compile the template and feed its output back to node-sass for compilation along with other SCSS partials
        done({
            contents: partial({ data: YOUR_DATA_OBJECT })
        });
    });
}

This approach allows us to include standard underscore variable syntax within our SCSS partials. For instance:

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

Answer №3

While working on a similar issue, I encountered the need to utilize SASS variables stored in a database to customize website themes based on individual client preferences.

After researching various options, I discovered a third-party service called . This platform provides a versatile solution that can be implemented across different programming languages to address this particular challenge.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process for adjusting the color of a div?

Is there a way to adjust the background color of a div when hovering over another div in HTML? <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.or ...

What is the secret behind Node.js's ability to efficiently manage multiple requests using just one thread

After conducting some research on the topic, I noticed that most people tend to focus solely on Non-blocking IO. For instance, if we consider a basic application that simply responds with "Hello World" text to the client, there will still be some executio ...

Changing a basic date format (11/26/2018) to a date type in Node.js

Request for assistance with converting frontend-sent date to type Date in node.js: Date: 11/26/2018 I need to convert this date into a type Date in order to compare it with other dates. How can I achieve this in node.js? ...

Fuzzy text upon scrolling in Internet Explorer version 6

Having a problem with text in Internet Explorer. When the page initially loads, the text appears clean and sharp. However, when I double-click on some white space of the page, the text becomes distorted as shown in the following image: Click here for alt ...

Tips for resolving the npm WARN message "package.json ... No README data"

After executing the command below npm install I encountered the following errors: npm WARN package.json <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8ae1ebf8e7eba7e9e2f8e5e7efa7e6ebffe4e9e2eff8cabaa4bba4be">[email  ...

Node.js console and endpoint are returning an object, but the JSON object cannot be fetched

Currently, I am working on a project for an online course where I am utilizing an NLP sentiment analysis API. Although I have made significant progress, I seem to be stuck at the final step. When I send the URL for analysis via the API call, I can see the ...

Ensuring the alignment of a personalized list bullet with the accompanying text

Looking to make the bullet point next to an li element larger? The common approach is to eliminate the standard point, add a custom one using the :before selector, and adjust the font size of the added point. While this technique somewhat achieves the goa ...

Guidance on running the node package manager from Sublime Text 2

Is there a way to access the node package manager directly from Sublime Text 2? I am interested in managing packages within the Sublime environment. ...

Is anything written to the database by Mongoose's ordered Model.insertMany if it fails?

According to the documentation on Model.insertMany, if options.ordered == true, the method will stop processing upon encountering the first error. https://mongoosejs.com/docs/api.html#model_Model.insertMany [options.ordered «Boolean» = true] If set t ...

Sliding Dropdown Menu with Dynamic Title Change Upon Selection

I'm having an issue with a dropdown menu on hover that slides down and then back up. The active list item serves as the title of the dropdown. When a user clicks on an <li>, it becomes active and receives the class active-markup-style which sets ...

Make sure to pause and wait for a click before diverting your

Having an issue with a search dropdown that displays suggestions when the search input is focused. The problem arises when the dropdown closes as soon as the input loses focus, which is the desired functionality. However, clicking on any suggestion causes ...

Tips for updating the hover effect color on Material UI select component options in a React JS application

I've been trying to customize the hover effect for the options in the mui Auto Complete component drop-down menu, but I'm having trouble finding the right method to do so. Here is the hover effect that I want to change: Image I'd like to u ...

Jquery Menu featuring subitems aligned to the right

I am experiencing an issue with my drop-down menu in ie6. The subitems menus are shifted to the right, rendering the menu useless. Here is the HTML code: <div id="navigation"> <a href="<?php echo base_url();?>" class="singl ...

How can I effectively troubleshoot an npm script using the features of vscode?

Currently, I am facing an issue with debugging an npm script using vscode. To troubleshoot this, I decided to set up a debug configuration and utilize the debugger feature in vscode. The npm script in question is: "scripts": { ... "dev": "node ta ...

Unable to extract a string from an object in Node.JS

I am attempting to connect my MongoDB database using Node.js with Express.js. I have tried creating a schema and parsing objects into JSON, but it doesn't seem to be working. Below is the content of my index.js file: var express = require('expre ...

Attempting to display divs at the bottom when hovering over menu options

I need help troubleshooting my HTML and CSS code. I want to be able to hover over a menu item and have the corresponding div appear at the bottom. Can you point out what's wrong with my code? <!DOCTYPE html> <html> <head> &l ...

Enhance Your Highcharts Funnel Presentation with Customized Labels

I am working on creating a guest return funnel that will display the number of guests and the percentage of prior visits for three categories of consumers: 1 Visit, 2 Visits, and 3+ Visits. $(function () { var dataEx = [ ['1 Vis ...

Receiving information within an Angular Component (Profile page)

I am currently developing a MEAN Stack application and have successfully implemented authentication and authorization using jWt. Everything is working smoothly, but I am encountering an issue with retrieving user data in the Profile page component. Here ar ...

When running npm start on a Windows system, an error message stating "Node.js version 14.9.0 detected" is displayed

Encountering an error with the npm start command: Node.js version v14.9.0 detected. The Angular CLI requires a minimum Node.js version of either v14.15, or v16.10. Please update your Node.js version or visit https://nodejs.org/ for additional instructions ...

Error: The constructor for JsSHA is not valid for the TOTP generator

Attempting to create a TOTP generator similar to Google's timed-based authenticator using the React framework. I am utilizing the bellstrand module for TOTP generation, but I am encountering issues when trying to import it into my React code. Here is ...