Adding SASS Support to Webpack

I set up an initial version of my Webpack configuration in my last article – it handled external libraries, sourcemaps, React/JSX and ES6. It was much easier to configure than I expected. However, there were some problems with how I left things. Most notably, I lost my stylesheet. Today I intend to get my stylesheet back using two different mechanisms, both based on Webpack. My stylesheet is based on SASS and I want to keep that format.

Mechanism 1: Inline Stylesheets

I’ve got my existing stylesheet from my gulp days in client/src/app.scss. It imports client/src/components/Application.scss. I can make a one-line change to my source code – in client/src/app.jsx – the JSX file, I can make the following modification:

import React from 'react';
import ReactDOM from 'react-dom';

// Components
import Application from './views/Application.jsx';

// Stylesheets
require('./app.scss');

// Render the application
ReactDOM.render(
    <Application/>,
    document.getElementById('rootelement')
);

This feels wierd – I am requiring the SASS file in my JSX code. Webpack will sort it out, but I need some more loaders in the configuration:

I need a few more build modules:

npm install --save-dev sass-loader css-loader style-loader node-sass

I also need to adjust my webpack.config.js so that it knows about the stylesheets:

module.exports = {
    entry: {
        grumpywizards: './client/src/app.jsx'
    },
    devtool: 'source-map',
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                loaders: [ 'babel' ],
                exclude: /node_modules/
            },
            {
                test: /\.scss$/,
                loaders: [ 'style', 'css', 'sass' ]
            }
        ]
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
    output: {
        filename: 'public/[name].js'
    }
};

There is something else I can do, though. I can associate my SASS files directly with the components. If I remove the @import call from the app.scss file, then include a require('./Application.scss') call in the Application.jsx file – things remarkably still work. This is a step in the right direction – each scss file is now imported separately and I can look at the css that is generated. However, I do have to be careful. One of the things I normally do is to abstract colors, fonts, and icons into partial scss files. If I include actual definitions (such as the @font-face css element) in those partial files, then they will be included multiple times in the generated CSS.

Time for sourcemaps. I need to see the original source code when I select a source map. I see the CSS code compiled to JavaScript instead. It looks like this:

exports = module.exports = require("./../../../node_modules/css-loader/lib/css-base.js")();
// imports


// module
exports.push([module.id, ".application{position:fixed;top:0;right:0;bottom:0;left:0;display:flex;flex-flow:column}.application-header{display:block;min-height:2rem;background-color:red;order:1}.application-main{display:block;background-color:#e0e0e0;order:2;flex-grow:1}.application-footer{display:block;min-height:2rem;background-color:lime;order:3}", ""]);

// exports



/*****************
 ** WEBPACK FOOTER
 ** ./~/css-loader!./~/sass-loader!./client/src/views/Application.scss
 ** module id = 6
 ** module chunks = 0
 **/

This doesn’t allow me to debug SASS files. I need to generate proper source maps. To do this, here is the syntax in your webpack.config.js:

module.exports = {
    entry: {
        grumpywizards: './client/src/app.jsx'
    },
    devtool: 'source-map',
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                loaders: [ 'babel' ],
                exclude: /node_modules/
            },
            {
                test: /\.scss$/,
                loaders: [ 'style', 'css?sourceMap', 'sass?sourceMap' ]
            }
        ]
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
    output: {
        filename: 'public/[name].js'
    }
};

You get the embedded source map by adding the sourceMap flag to both CSS and SASS. You can see the appropriate markup by going to an element in Chrome Dev Tools and then clicking on the filename:

2016-01-18-1

Mechanism 2: A Separate CSS file

If you want to switch over to having a separate file, you only need to change your HTML file – this will bring in the resulting CSS file. Webpack nicely converts the SASS files to CSS and bundles them for us. What we need to do is to extract the generated CSS and store it as a separate file. Fortunately, there is a recipe for that and all the work is done within the webpack.config.js:

var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: {
        grumpywizards: './client/src/app.jsx'
    },
    devtool: 'source-map',
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                loader: 'babel',
                exclude: /node_modules/
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract(
                    'style', // The backup style loader
                    'css?sourceMap!sass?sourceMap'
                )
            }
        ]
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
    output: {
        filename: 'public/[name].js'
    },
    plugins: [
        new ExtractTextPlugin('public/grumpywizards.css')
    ]
};

There are a couple of wrinkles here.

  1. I’m bringing in another module to handle the actual construction of the CSS file.
  2. I’m using loader instead of loaders. The loader option takes a string of loaders separated by the bang ! symbol.

This still generates a map file, per the devtool option. Now all you need to do is add the css file to your index.html with a link statement and you can continue working as before.

Bonus Coverage: Handling Imports

One of the things that bugs me about this setup is that imports are all relative. In other words, let’s say I have a directory client/style which contains a _colors.scss file with a bunch of variables that define my color scheme. I have to import that like this:

@import '../../style/_colors.scss';

That relative path is really annoying. I have to be concerned with that relative path if I move that component to another directory. Fortunately, node-sass has a configuration option for just this case – the includePaths option. If I set the includePaths to client/style, then I can import the file like this:

@import 'colors';

Even better, I don’t have to be concerned with adjusting paths if I move the component around. I can configure this in the webpack.config.js like this:

var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: {
        grumpywizards: './client/src/app.jsx'
    },
    devtool: 'source-map',
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                loader: 'babel',
                exclude: /node_modules/
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract(
                    'style', // The backup style loader
                    'css?sourceMap!sass?sourceMap'
                )
            }
        ]
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
    output: {
        filename: 'public/[name].js'
    },
    sassLoader: {
        includePaths: [ 'client/style' ]
    },
    plugins: [
        new ExtractTextPlugin('public/grumpywizards.css')
    ]
};

In fact, I can define any other node-sass options in the same way.

More Bonus Coverage: Fixing Mocha

An alert reader might have run npm test at this point. If you haven’t, here is the spoiler. It doesn’t work. The problem is that the SASS files are required within the code. this is not a problem within Webpack – it’s handled by the loaders – but it’s definitely a problem outside of Webpack, like in a mocha test run. Fortunately, there is an easy solution – Brandon Konkle wrote a small module called ignore-styles which you can require in your mocha call. After you have used npm install --save-dev ignore-styles, you can update your script call within package.json like this:

  "scripts": {
    "clean": "rimraf public",
    "pretest": "eslint client/src server/src client/test server/test && sass-lint -v -q -f stylish",
    "test": "mocha --require ignore-styles --recursive --compilers js:babel-register --reporter dot client/test server/test",
    "prestart": "webpack -p",
    "start": "node ./bin/www"
  },

Once that is done, your tests will work again.

Wrap-up

There are other ways to work with style, especially with React and Web Components. I’m certainly going to explore those options in the next few months. However, the workflow I have generated here allows me to associate the CSS for a component with that component. A side effect is in maintainability. I don’t have to think about removing the CSS when I remove a component from my build. It’s done automatically for me. I think that alone is worth the price of learning a new build system.

You can check out the resulting build system at my GitHub repository.