Skip to content
esr360 edited this page May 26, 2019 · 18 revisions

Import JavaScript/JSON files into Sass

Synergy-Sass-Importer allows you to import JavaScript/JSON/json5 files into your Sass file. It was built for the Cell library (which in turn was built for the Synergy framework) but can be used with any projects that use Node.js and Sass.

Setup

This module hooks into node-sass's importer api.

var sass = require('node-sass');
var SynergySassImporter = require('@onenexus/synergy-sass-importer');

// Example 1
sass.render({
    file: scss_filename,
    importer: SynergySassImporter,
    ...options
}, function(err, result) { /*...*/ });

// Example 2
var result = sass.renderSync({
    data: scss_content
    importer: [SynergySassImporter, someOtherImporter]
    ...options
});

node-sass command-line interface

To run this using node-sass CLI, point --importer to your installed json importer, for example:

node-sass /PATH/TO/app.scss --importer node_modules/synergy-sass-importer/dist/synergy-sass-importer.js

Webpack / sass-loader

ES6 Imports
import SassJSONImporter from '@onenexus/sass-json-importer';
CommonJS
const SassJSONImporter = require('@onenexus/sass-json-importer');
Configuration
{
    test: /\.scss$/,
    use: [
        {
            loader: 'sass-loader', 
            options: {
                importer: SynergySassImporter
            }
        }
    ]
}

Usage

Once your Sass compiler has been setup to use Synergy-Sass-Importer, you can begin to import JavaScript/JSON files in your .scss files. The purpose of Synergy-Sass-Importer is to provide configuration for Synergy modules, but this essentially can be translated to "provide data to Sass components". This usage guide will assume you are using Synergy-Sass-Importer to provide configuration for some UI component that you are styling with Sass, with a rational of being able to share configuration between Sass and JavaScript. If you are using Synergy-Sass-Importer for some other purpose, the guide should hopefully still be of some use.

JavaScript

Using JavaScript to handle your component's configuration is the most flexible means to do it. It allows you to easily use framework-agnostic JavaScript-based themes within your project as well as allows for logic within your component's configuration. Configuration should be exported from its own file.

File Exports an Object

If your JavaScript configuration file exports a plain JavaScript object, please see the JSON section for static configuration where the same rules apply. If you require themeing or any sort of logic, consider exporting a function instead.

File Exports a Function

This is the most flexible way to handle a UI component's configuration (learn more). It allows you to use themes and easily share properties accross components. Simply export a function that takes an optional single parameter as the input. The parameter will expose the project's theme (learn more). The function should return an object. The object will be converted to a Sass map and attached to a variable named after the file which is imported (e.g $config from a config.js file) and then made available in to Sass.

config.js
export default (theme) => ({
    'name': 'myModule',
    'color': theme.colors.secondary,
    ...
})
styles.scss
@import 'config.js'; // `$config` will now be defined in Sass

.mySelector {
    color: map-get($config, 'color');
}

Theme

Using JavaScript for configuration allows you to expose a theme to your configuration (as seen in the above example - learn more), allowing you to share properties between components. Your theme should exist as a separate JavaScript file which should export either an object or a function which returns an object. This object will be passed as the argument to your component's configuration function.

See the Themes section for more information on themes

JSON

JSON files are useful for static configuration, but if you require themeing or any sort of logic, consider using JavaScript instead. The JSON object will be converted to a Sass map and attached to a variable named after the file which is imported (e.g $config from a config.json file) and then made available in to Sass.

/config.json
{
    "foo": "red",
    "bar": {
        "qux": "10px"
    }
}
/styles.scss
@import 'config.json';

.fizz {
    color: map-get($config, 'foo');
    height: map-get(map-get($config, 'bar'), 'qux');
}
Output
.fizz {
    color: red;
    height: 10px;
}
Using Synergy/Cell

If using Synergy or Cell, you can use the this() function to retreive configurayion from a $config variable:

@import 'config.json';

.fizz {
    color: this('foo');
    height: this('bar', 'qux');
}

Themes

There are several uses for themes:

  • share properties between imported JavaScript files that export a function
  • merge/overwrite properties returned from other imports (overwrite module configuration, in Synergy terms)
  • expose a $theme variable to Sass with your theme's evaluated properties assigned

Themes can be imported into Sass or passed via the global Node object.

Importing Theme Into Sass

You can import a theme into your module's Sass file along with any other imports (i.e a config.js file), and Synergy-Sass-Importer will do the rest. If any other subsequent imports (e.g. config.js) export a function, the theme will be supplied as the argument to the function. The theme will also be exposed to Sass under the $theme variable.

In order for Synergy-Sass-Impoter to know that an imported file is intended to be a theme, it must either be called theme.{js|json} or have a direct parent/grand-parent directory called themes

src/themes/myTheme.js
export default (theme) => ({
    someProperty: true,
    colors: {
        primary: 'red',
        secondary: 'green'
    },
    sizes: {
        primary: '16px',
        secondary: '12px'
    },
    someThing: {
        // properties which access `theme` must be passed as functions
        someProperty: () => theme.colors.primary // 'red'
    },
    // Module configuration can be overwritten by themes (see - https://git.io/fjBww)
    modules: {
        Accordion: {
            'font-size': () => theme.sizes.primary // '16px',
            'font-weight': 'bold' // static values do not need to be passed as functions
        }
    }
    ...
});
src/modules/Accordion/config.js
export default (theme) => ({
    'color': theme.colors.secondary, // green
    'font-size': theme.sizes.secondary // this gets overwritten by the above theme
});
src/modules/Accordion/styles.js
@import '../../../themes/myTheme.js';
@import 'config.js';

.accordion {
    color: map-get($config, 'color'); // green
    font-size: map-get($config, 'font-size'); // 16px
    font-weight: map-get($config, 'font-weight'); // bold
}

.someThing {
    color: if(map-get($theme, 'someProperty'), 'pink', 'orange'); // pink
}

Checkout sass-resources-loader for sharing resources between Sass files using Webpack if you don't want to import your theme into every Sass file

Passing Theme To Sass Through Node.js

Instead of having to import the theme into each file (which could become problematic if you want to change themes and have to update a bunch of paths), you can instead pass the theme through Node.js where it will be picked up by Synergy-Sass-Importer - learn more.

Global/Foundation Theme

It's possible that your various themes may share many properties which are not prone to changing on a theme-to-theme basis. If this is the case, consider creating a foundation theme on which to base subsequent themes. Unlike regular themes, a foundation theme can only be an object (and not a function that returns an object). These values can be exposed to your themes when your theme exists as a function (they will be supplied via the theme argument, which the theme function itself mutates, allowing you to access new theme values from within the theme itself).

Import Foundation Theme Into Sass

You can import a foundation theme into your module's Sass file along with your new theme and config.js file, and Synergy-Sass-Importer will do the rest.

In order for Synergy-Sass-Impoter to know that an imported file is intended to be a foundation theme, it must either be called foundation.{js|json} or have a direct parent directory called foundation

src/foundation/index.js
export default {
    someProperty: true,
    colors: {
        primary: 'red',
        secondary: 'blue'
    },
    sizes: {
        primary: '16px',
        secondary: '12px'
    }
};
src/themes/myTheme.js
export default (theme) => ({
    someProperty: false,
    colors: {
        secondary: 'green'
    },
    someNewThing: {
        // properties which access `theme` must be passed as functions
        someProperty: () => theme.colors.primary // 'red'
    },
    // Module configuration can be overwritten by themes (see - https://git.io/fjBww)
    modules: {
        Accordion: {
            'font-size': () => theme.sizes.primary // '16px',
            'font-weight': 'bold' // static values do not need to be passed as functions
        }
    }
    ...
});
src/modules/Accordion/config.js
export default (theme) => ({
    'color': theme.colors.secondary, // green
    'font-size': theme.sizes.secondary // this gets overwritten by the above theme
});
src/modules/Accordion/styles.js
@import '../../../foundation/index.js';
@import '../../../themes/myTheme.js';
@import 'config.js';

.accordion {
    color: map-get($config, 'color'); // green
    font-size: map-get($config, 'font-size'); // 16px
    font-weight: map-get($config, 'font-weight'); // bold
}

.someThing {
    color: if(map-get($theme, 'someProperty'), 'pink', 'orange'); // orange
}

Checkout sass-resources-loader for sharing resources between Sass files using Webpack if you don't want to import your foundation and theme into every Sass file

Passing Foundation Theme Through Node

Instead of having to import the foundation theme into each file, you can instead pass the foundation object through Node.js where it will be picked up by Synergy-Sass-Importer - learn more.

Merge Values From Theme

In order to merge and overwrite configuration properties from a theme, the file which is imported and parsed by Synergy-Sass-Importer must have a parent directory whose name corresponds to an entry in the modules object of your theme (learn more). Taking an Accordion module as an example:

someTheme.json
{
    "someProperty": true,
    "modules": {
        "Accordion": {
            "color": "red"
        }
    }
}

...then the file to be imported into your Sass (and processed by Synergy-Sass-Importer) must have a parent directory called Accordion (the directory does not have to be a direct parent):

src/modules/Accordion/assets/config.js
export default {
    color: 'blue',
    fizz: 'buzz'
}

The modules.Accordion object will be merged with the config object and made available to Sass under the $config variable (the name of the variable will epend on the name of the file that was imported, e.g. $config from config.js), and the theme object will be made available to Sass under the $theme variable:

src/modules/Accordion/assets/styles.scss
@import '../../../themes/myTheme.js';
@import 'config.js';

@debug map-get($config, 'color'); // 'red'
@debug map-get($config, 'fizz'); // 'buzz'
@debug map-get($theme, 'someProperty'); // true

Clone this wiki locally