Share functions and variables between JS and Sass.
Supports both Node Sass and Dart Sass.
npm install jsass
Since Node Sass v3.0.0, and Dart Sass v1.0.0-beta.5.1 the functions
option makes it possible to pass an Object of JS functions that may be invoked by the sass files being compiled (check Node Sass docs or Dart Sass docs for more information).
This gives the developers unlimited possibilities for processing Sass data during build-time, but since the connected JS functions receive their parameters and must return their return values in Sass's own data types, this makes a bit more difficult to just use this feature out-of-the-box.
This package contains a class (JSFunctionsToSass), which acts like a middleware between Sass and the connected JS function. It converts the received Sass type arguments to their JS equivalents, and applies them as pure JavaScript types to the connected JS function. When the function returned what it has to be, JSFunctionsToSass converts the returned JS value back to its Sass equivalent, and passes back to Sass. So simple.
Both sync and async functions are supported.
JSFunctionsToSass makes it possible to automatically define the parameters of the Sass function.
If the Sass function signature (the keys of the Sass options.functions
object) doesn't have a parameter block, it tries to get the parameter names of the JS function, and define the same parameter names also in the Sass function. If we cannot get the parameter names of the given JS function (most probably it doesn't have explicitly declared parameters), we add a single spread parameter for the Sass function ($arguments...
), making it possible to wrap JS functions which use eg. arguments
in their body.
This functionality is limited only to Sass function signatures without a parameter block (parentheses immediately after the function's name). Sass functions having defined parameters remain untouched, and the same applies if explicitly zero parameters are defined (when the parameter block is empty).
Note: JSFunctionsToSass
cannot detect (and therefore define) the default values of the function parameters. If Sass throws error because of a missing (but only optional) argument, you can fall back anytime to the original Sass signature syntax.
The automatic parameter resolution is completely optional and can be used in parallel with the original syntax.
const path = require('path');
const sass = require('node-sass');
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass();
sass.render({
file: path.resolve('styles.scss'),
functions: jsFunctionsToSass.convert({
// Explicitly defined Sass parameter names
'str-replace($string, $search, $replace: "")': function str_replace(string, search, replace) {
if (typeof string !== 'string') {
throw new Error('str-replace needs `$string` to be typeof string!');
}
return string.replace(new RegExp(search, 'g'), replace);
}
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
const path = require('path');
const sass = require('node-sass');
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass();
sass.render({
file: path.resolve('styles.scss'),
functions: jsFunctionsToSass.convert({
// Sass parameter names resolved from the JS function
str_replace: function str_replace(string, search, replace) {
if (typeof string !== 'string') {
throw new Error('str-replace needs `$string` to be typeof string!');
}
return string.replace(new RegExp(search, 'g'), replace);
}
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
const path = require('path');
const sass = require('node-sass');
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass();
const str_replace = require('./str_replace');
sass.render({
file: path.resolve('styles.scss'),
functions: jsFunctionsToSass.convert({
// Passing only a JS function reference
str_replace
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
const path = require('path');
const sass = require('node-sass');
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass();
/**
* This example demonstrates the simplest usage of JSFunctionsToSass, adding a `str-replace` function to Sass
*/
sass.render({
file: path.resolve(__dirname, './str-replace.scss'),
functions: jsFunctionsToSass.convert({
'str-replace': function (string, search, replace) {
if (typeof string !== 'string') {
throw new Error('str-replace needs `$string` to be typeof string!');
}
return string.replace(new RegExp(search, 'g'), replace);
}
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
str-replace.scss
.string-replace {
$string: 'The answer to life the universe and everything is 42.';
string: '#{$string}';
search: 'e';
replace: 'xoxo';
content-replaced: '#{str-replace($string, 'e', 'xoxo')}';
}
Output:
.string-replace {
string: "The answer to life the universe and everything is 42.";
search: 'e';
replace: 'xoxo';
content-replaced: "Thxoxo answxoxor to lifxoxo thxoxo univxoxorsxoxo and xoxovxoxorything is 42.";
}
const path = require('path');
const sass = require('sass'); // Now we are using `dart-sass`
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass({ implementation: sass });
const _ = require('lodash');
/**
* This example implements an advanced `map-get` function in Sass, using lodash's `get()`, making it possible to get the value of a nested Map (or List) by its path (eg. `deeply.nested.value`).
*/
sass.render({
file: path.resolve(__dirname, './map-get-super.scss'),
functions: jsFunctionsToSass.convert({
'map-get-super($map, $path)': _.get
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
map-get-super.scss
$map: (
'deeply': (
'nested': (
'value': 'jSass'
)
)
);
.resolved {
map: '#{$map}';
path: 'deeply.nested.value';
content: '#{map-get-super($map, 'deeply.nested.value')}';
}
Output:
.resolved {
map: '("deeply": ("nested": ("value": "jSass")))';
path: 'deeply.nested.value';
content: "jSass";
}
const path = require('path');
const sass = require('node-sass');
const { JSFunctionsToSass } = require('jsass/dist/node');
const jsFunctionsToSass = new JSFunctionsToSass();
const urljoin = require('url-join');
/**
* This example adds a `url-join` function to Sass, using the `url-join` NPM package
*/
sass.render({
file: path.resolve(__dirname, './url-join.scss'),
functions: jsFunctionsToSass.convert({
'url-join($paths...)': urljoin
})
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
url-join.scss
.url-join {
content: '#{('https://', 'github.com', 'body-builder', 'jsass')}';
content-joined: '#{url-join('https://', 'github.com', 'body-builder', 'jsass')}';
}
Output:
.url-join {
content: "https://, github.com, body-builder, jsass";
content-joined: "https://github.com/body-builder/jsass";
}
jSass makes a good pair with sass-extract. We recommend using sass-extract to export your Sass project's variables to JS. Once you are installed it, why don't you query it easily right in your frontend JavaScript code? JSass
makes this task easy for you. It reads the output Object of sass-extract
, and converts it to simple JavaScript Strings, Arrays or Objects. You can
If you would like to share some of your CSS selectors with JS, JSass_mod_jQuery
makes it possible to use your Sass selectors directly in jQuery.
In addition to this, jSass's sass-extract plugin helps you to extract only the really important data to your bundle file.
Sass accepts a data
option as the source code to compile (check Node Sass docs here or Dart Sass docs here). This also makes it possible to pass for example Node.js variables to your Sass files. However, data
must be a string, containing syntactically valid raw Sass code. jSass helps you to stringify any general JavaScript types to their equivalent Sass variable type to raw Sass format. It supports both SCSS and SASS (indented) syntax as output type.
const { JSVarsToSassData } = require('jsass/dist/node');
const jsVarsToSassData = new JSVarsToSassData();
process.env.NODE_ENV = 'development';
const data = jsVarsToSassData.convert({
ENV: process.env.NODE_ENV,
DEV: process.env.NODE_ENV === 'development',
variable: 'variable',
importantList: ['some', 'important', 'value'],
importantMap: {
bool: true,
string: 'string',
variable: '$variable',
color: '#646e64',
unit: '12px'
},
nestedValues: {
array: ['some', 'important', 'value'],
map: {
bool: true,
string: 'string',
variable: '$variable',
color: 'rgba(100, 110, 100, 0.5)',
unit: '12px'
},
thatsAll: false
}
});
console.log(data);
Output:
$ENV: 'development';
$DEV: true;
$variable: 'variable';
$importantList: ('some', 'important', 'value');
$importantMap: ('bool': true, 'string': 'string', 'variable': $variable, 'color': rgb(100, 110, 100), 'unit': 12px);
$nestedValues: ('array': ('some', 'important', 'value'), 'map': ('bool': true, 'string': 'string', 'variable': $variable, 'color': rgb(100, 110, 100), 'unit': 12px), 'thatsAll': false);
Use it in Sass:
const fs = require('fs');
const path = require('path');
const sass = require('node-sass');
const { JSVarsToSassData } = require('jsass/dist/node');
const jsVarsToSassData = new JSVarsToSassData();
/**
* This example demonstrates how to inject JS variables to Sass using the `data` option of `sass.render()`
*/
const data = jsVarsToSassData.convert({
ENV: process.env.NODE_ENV,
DEV: process.env.NODE_ENV === 'development',
variable: 'variable',
importantList: ['some', 'important', 'value'],
importantMap: {
bool: true,
string: 'string',
variable: '$variable',
color: '#646e64', // By default we are using this value, just edit your `node_sass.scss` to see other injected values
unit: '12px'
},
nestedValues: {
array: ['some', 'important', 'value'],
map: {
bool: true,
string: 'string',
variable: '$variable',
color: 'rgba(100, 110, 100, 0.5)',
unit: '12px'
},
thatsAll: false
}
});
const file = fs.readFileSync(path.resolve('./node_sass.scss'), 'utf8');
sass.render({
data: [data, file].join('\n'),
}, (err, result) => {
if (err) {
throw new Error(err);
}
console.log(result.css.toString());
});
node_sass.scss
.selector {
@if ($DEV == true) {
background-color: map_get($importantMap, 'color');
} @else {
background-color: white;
}
}
Output:
.selector {
background-color: #646e64;
}
Use jSass's jsVarsToDefinePlugin
to be able to safely share the same values with your JS and Sass files:
webpack.config.js
const webpack = require('webpack');
const jsVarsToDefinePlugin = require('jsass/dist/jsVarsToDefinePlugin');
module.exports = {
...,
plugins: [
new webpack.DefinePlugin(jsVarsToDefinePlugin({
ENV: process.env.NODE_ENV,
DEV: process.env.NODE_ENV === 'development',
variable: 'variable',
importantList: ['some', 'important', 'value'],
importantMap: {
bool: true,
string: 'string',
variable: '$variable',
color: '#646e64',
unit: '12px'
},
nestedValues: {
array: ['some', 'important', 'value'],
map: {
bool: true,
string: 'string',
variable: '$variable',
color: 'rgba(100, 110, 100, 0.5)',
unit: '12px'
},
thatsAll: false
}
}))
]
};
See other examples in the examples directory. ($ npm run start
in any folder.)
npm run build
npm run test
Sponsored by: SRG Group Kft.