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 @import
s 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 @import
ed 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 require
able 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.
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.
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
- Why You Shouldn’t Use Inline Style with React
- Automatic CSS Namespacing with React
- Learn Raw React
- LESS Parent Selector
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
I get the same problem. Very frustrating.
I think autoprefixer-loader is now deprecated. Should maybe update to postcss-loader.
Great article otherwise.
// Load plain-ol’ vanilla CSS
{ test: /\.scss$/, loader: “style!css” },
I think your regexp is wrong here.
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 ! 😉