Idiot Note: JSS (CSS-in-JS)

Today, we're going to talk about one unique Javascript library, that is JSS.

What is it?

JSS, or CSS-in-JS, is a Javascript library that allows you to write CSS styles through Javascript objects. With JSS, you wouldn't write any .css file, rather you would write all styles in your Javascript files.

How to use it?

We'll use Webpack to demonstrate the use of JSS. We'll make a simple web page that consists of 2 modules: Card and Button module.

Here are the steps:

  1. Install webpack and webpack-cli in your project.
    npm install webpack webpack-cli --save-dev​
  2. Install jss and jss-preset-default in your project.
    npm install jss jss-preset-default --save-dev​
  3. Here's the project structure. Notice that there are no CSS files anywhere. Weird, right?
    .
        ├── dist
        │   ├── index.html
        ├── src
        │   ├── button
        │   |   ├── index.js
        │   ├── card
        │   |   ├── index.js
        │   ├── index.js
        ├── package.json
        └── webpack.config.js​

    dist/index.html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Example JSS</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <div id="root"></div>
        
        <script src="bundle.js"></script>
    </body>
    </html>

    src/button/index.js
    This is where we define Button module. Notice that all styles are defined in the style variable.

    import { create } from 'jss';
    import preset from 'jss-preset-default';
    
    // create JSS instance
    const jss = create();
    
    // load default plugins
    jss.setup(preset());
    
    // define styles
    const style = {
        container: {
            border: '1px solid gray',
            padding: '10px',
            background: 'none',
            cursor: 'pointer',
        },
        '@media (min-width: 1024px)': {
            container: {
                padding: '20px',
            }
        }
    };
    
    // create style sheet
    const { classes } = jss.createStyleSheet(style).attach();
    
    export function Button(text) {
        var btn = document.createElement('button');
        btn.className = classes.container; // use style sheet
        btn.textContent = text;
    
        return btn;
    }

    src/card/index.js
    Similar to the previous file, this is where the Card module is defined. Notice that both modules use a CSS class named container. This is totally fine and won't cause any conflict, as JSS will rename all CSS class names later.

    import {create} from 'jss';
    import preset from 'jss-preset-default';
    
    const jss = create();
    
    jss.setup(preset());
    
    const style = {
        container: {
            border: '1px solid black',
            padding: '20px',
        },
    };
    
    const {classes} = jss.createStyleSheet(style).attach();
    
    export function Card() {
        var div = document.createElement('div');
        div.className = classes.container;
    
        return div;
    }

    src/index.js

    import { Button } from './button/index.js';
    import { Card } from './card/index.js';
    
    function app() {
        var el = Card();
        el.appendChild(Button('button 1'));
    
        return el;
    }
    
    var root = document.getElementById('root');
    root.appendChild(app());

    webpack.config.js

    // webpack.config.js
    const path = require('path');
    
    module.exports = {
        entry: './src/index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        watch: true,
    };
  4. Run Webpack command: npx webpack to bundle all files into dist/bundle.js.
  5. Open dist/index.html file and you'll get this result:

That's the basic of JSS!

Now, we'll delve into the the most important part of JSS, which is its plugins.

In the example above, we use the default plugins by using jss-preset-default. By default, jss-preset-default will load these following plugins:

  1. jss-camel-case
    This plugin enables you to use camelCase when writing CSS properties. For example:
    // before
    const style = {
        container: {
            'border-radius': '5px'
        }
    };
    
    // after
    const style = {
        container: {
            borderRadius: '5px'
        }
    };​
  2. jss-compose
    This plugin enables you to compose CSS classes with other classes. For example:
    // before
    const style = {
        clickable: {
            cursor: 'pointer',
        },
        container: {
            borderRadius: '5px'
        }
    };
    btn.className = classes.container+' '+classes.clickable;
    
    // after
    const style = {
        clickable: {
            cursor: 'pointer',
        },
        container: {
            composes: ['$clickable'],
            borderRadius: '5px'
        }
    };
    btn.className = classes.container;​
  3. jss-default-unit
    This plugin enables you to omit the units when writing CSS property values. For example:
    // before
    const style = {
        container: {
            borderRadius: '5px'
        }
    };
    
    // after
    const style = {
        container: {
            borderRadius: 5
        }
    };​
  4. jss-expand
    This plugin offers a better syntax for writing CSS properties. For example:
    // before
    const style = {
        container: {
            borderRadius: '5px',
            boxShadow: '1px 1px 5px 5px black'
        }
    };
    
    // after
    const style = {
        container: {
            borderRadius: '5px',
            boxShadow: {
                x: 1,
                y: 1,
                blur: 1,
                spread: 1,
                color: 'black'
            }
        }
    };
  5. jss-extend
    This plugin enables you to extend CSS classes with other classes. For example:
    // before
    const style = {
        container: {
            borderRadius: '5px',
        },
        blueContainer: {
            borderRadius: '5px',
            background: 'blue'
        }
    };
    
    // after
    const style = {
        container: {
            borderRadius: '5px',
        },
        blueContainer: {
            extend: 'container',
            background: 'blue'
        }
    };​​
  6. jss-global
    This plugin enables you to define global CSS class in JSS (although this is not recommended). For example:
    const style = {
        '@global': {
            body: {
                background: 'green'
            }
        },
        container: {
            borderRadius: '5px',
            boxShadow: '1px 1px 5px 5px black'
        }
    };​
  7. jss-nested
    This plugin enables nesting in your CSS classes. For example:
    const style = {
        container: {
            borderRadius: '5px',
            '&:hover': {
                background: 'blue'
            }
        }
    };​
  8. jss-props-sort
    This plugin automatically reorders your CSS properties so that they don't override each other. For example:
    const style = {
        container: {
            borderLeft: '1px solid green',
            border: '2px solid black',
        }
    };​

    This will be compiled to this CSS:

    .c001 {
        border: 2px solid black;
        border-left: 1px solid green;
    }​
  9. jss-template
    This plugin enables the use of string template for CSS property values. For example:
    // before
    const style = {
        container: {
            borderLeft: '1px solid green',
            border: '2px solid black',
        }
    };
    
    // after
    const style = {
        container: `
            border-left: 1px solid green;
            border: 2px solid black;
        `
    };​
  10. jss-vendor-prefixer
    This plugin automatically handles vendor prefixing for you. For example:
    const style = {
        container: {
            transform: 'translateX(20px)'
        }
    };​
    This will be compiled to:
    .c001 {
        transform: -webkit-translateX(20px);
    }​

That's all of it!