Writing Happy Stylesheets with Webpack

While the last few years have seen JavaScript turn from a tangle of jQuery into an orderly affair, CSS has been the subject of neglect. While JavaScript has learned new tricks like modularity, components and dependency bundling, most stylesheets are still a monolithic mess of globals.

But with the advent of Webpack, it is time for stylesheets to shine again. In fact, many of the lessons which JavaScript has learned can now be applied to SCSS and LESS too — leaving your stylesheets clean, independent, and most importantly, happy.

Stylesheets are Happier in Components

While stylesheets can apply styles to HTML tags like div or ul, it leaves them feeling lonely. How can they be sure that such nonspecific selectors will stay in use? How can they be sure that they won’t be applied to an element which doesn’t want them?

And while simple class selectors like .active or .container may temporarily give the stylesheet’s life some meaning, they still won’t result in peace-of-mind. Applications grow; what if the stylesheet’s selectors are forgotten in favour of younger ones? What if other stylesheets select the same classes, causing an abomination of merged styles?

But when stylesheets are paired with a JavaScript component — whether Angular or React — they feel happy. With small CSS files which only style a single component’s HTML, they know that their selectors have meaning. They know that with class names namespaced using CSS modules or Pacomo, younger stylesheets will never be able to apply competing styles. And when living in the same directory as their component, they know that their fate will be shared — wherever it may lead.

Stylesheets value their independence

Traditionally, SCSS or LESS stylesheets are bound by the whims of their parents; they depend on a number of global variables, mixins and classes defined in a main.scss which @imports them.

But stylesheets don’t want to stay at the family home forever — they long for a day when they are independent. They yearn to be require()‘d by their JavaScript component. And to grant stylesheets their wish, you’ll need to rewrite them such that they don’t depend on the environment their parent provides — any dependencies must be manually @imported from within the stylesheets themselves.

You see, happy stylesheets are independent stylesheets.

Intermission: How to require() stylesheets from JavaScript

The reason that stylesheets have traditionally been imported from a main.scss is that you literally couldn’t require them from your JavaScript. Since all stylesheets were then compiled at once, they couldn’t individually import their own dependencies. But with Webpack, anything is possible.

If you haven’t got Webpack set up already, start by following my guide to configuring Webpack with very few lines. But assuming you have got Webpack working, making CSS requireable is as simple as adding a loader:

// Pick _one_.

loaders: [
  // Load LESS
  { test: /\.less$/, loader: "style!css!autoprefixer!less" },

  // Load SCSS
  { test: /\.scss$/, loader: "style!css!autoprefixer!sass" },

  // Load plain-ol' vanilla CSS
  { test: /\.scss$/, loader: "style!css" },
]

Make sure you npm install the loader modules: style-loader, css-loader, autoprefixer-loader and less-loader or sass-loader.

Sharing variables and mixins is caring

Even without access to their parent’s environment, stylesheets can still share variables and mixins. But they’ll need to do so as peers — not children.

To facilitate this, shared variables and mixins need a home, and what better place than a theme directory. Stylesheets can then @import only the libraries that they need. And since these themes only contain variables and mixins, they can even be packaged and shared!

But beware! When selectors creep into this theme directory, they’ll infect every stylesheet which imports them. Stylesheets like to stay fit and healthy; importing globals is sure to result in tears.

A note to Stylesheets from the land of LESS

LESS, in its great wisdom, provides the @import (reference) command. Use this command when importing theme dependencies to banish any globals to the depths of /dev/null.

Shun the globals. Shuuuunnnnnnn.

But often, globals are a necessary evil. Sometimes you can’t avoid them, no matter how hard you try:

@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');

html, body {
  position: relative;
  height: 100%;
  min-height: 100%;
}

In cases like this, globals must be placed as far away from stylesheets as possible. Create a globals.less or globals.css file in a different location from the theme directory. Move any globals there, and then throw away the key.

But seriously, where is my distributable CSS file?

Nice story. But where have you put my distributable CSS file? GIVE ME BACK MY CSS FILE!

Ok, calm down. All you need is a little more webpack configuration.

First, you’ll need to install the extract-text-webpack-plugin module:

npm install extract-text-webpack-plugin --save

Then, you’ll need to import it at the top of your webpack.config.js:

// Boring CommonJS JavaScript
var ExtractTextPlugin = require("extract-text-webpack-plugin");

// Exciting ES6 JavaScript
import ExtractTextPlugin from "extract-text-webpack-plugin";

Next, in your production configuration, replace the loader configuration from above with the ExtractTextPlugin version:

// Pick _one_.

loaders: [
  // Extract LESS
  { test: /\.less$/,
    loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader") },

  // Extract SCSS
  { test: /\.scss$/,
    loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader") },

  // Extract plain-ol' vanilla CSS
  { test: /\.scss$/,
    loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
]

Finally, add this to the plugins array in your production Webpack config:

new ExtractTextPlugin("style.css", {allChunks: false})

You don’t need ExtractTextPlugin in development. Use your browser’s debugging tools to view the loaded CSS instead.

If you want to see this in action, or don’t want to do it yourself, or just like reading other people’s code, then check out the Unicorn Standard Starter Kit.

Wow, my CSS feels structured now! Can you structure my JavaScript, too?

Why, certainly — I’m so glad that you asked! I’m currently in the midst of a series on structuring Small Scale React applications, and the only way to make sure you don’t miss out on the next part is to join my Newsletter!

And to sweeten the deal, in return for your e-mail you’ll immediately receive three print-optimised PDF cheatsheets. One on React (see preview), another for ES6 and yet another for JavaScript promises. All for free!

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

One more thing – I love hearing your opinions, questions, and offers of money. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!

Read More

Related Projects

5 Comments Writing Happy Stylesheets with Webpack

  1. Daniel

    James,

    Thanks for the article.

    One problem I am have is trying to use the ES6 import statement vs require. No problems when using require. I am using webpack and have babel setup with presets but I keep getting Unexpected token import. Thanks, and if it is easier I can provide my config file or vice versa

    Reply
  2. Andrew

    I think autoprefixer-loader is now deprecated. Should maybe update to postcss-loader.

    Great article otherwise.

    Reply
  3. Monkey Monk

    And what if I want to get all that Sass code and make a file that will be imported in the whole Sass “thing” ? 🙂

    Thank you for the article BTW ! 😉

    Reply

Leave a Reply

Your email address will not be published.