PostCSS-modules: Isolate ’em all!

Cover for PostCSS-modules: Isolate ’em all!

Topics

We’ve been struggling with global CSS for so many years. Now is the time to stop it. No matter what language or framework you use, CSS name clashing will never be an issue anymore. I’ll show you how to use PostCSS and PostCSS-modules to have it automatically dealt with at the server side.

CSS was originally a styling tool for documents. Many things have changed since 1996. Browsers are not document viewers anymore. Сhatting, working, gaming—there is hardly anything that browsers don’t do.

Nowadays, we go much farther than marking text up in HTML and developing content websites with CSS. We use CSS to the full of its potential, creating things it can hardly handle.

Every seasoned software developer knows—once you’ve used global namespaces, you’ve opened a can of worms. The trouble won’t be long in coming. If you don’t provide unique names for things in your code, you’ll inevitably face name conflicts, various side effects, and an unmaintainable codebase.

For CSS, that means buggy layouts, epic battles with specificity, tremendous selectors and countless hours of CSS debugging. Just because every selector can target unwanted elements and clash with other selectors.

Pretty much every programming language supports modules with isolated scopes. Even JavaScript, which always goes hand in hand with CSS, has got the AMD, CommonJS, and, finally, ES6 modules. However, we don’t have any modules for CSS at all.

Isolated components are an exсellent option for non-trivial applications—they are small, independent, and we can use them as bricks to build more complex ones. But we still have an issue: how to prevent global name clashing?

Methodologies

Thanks to talented folks, now we have Object-Oriented CSS, BEM, SMACSS and many other variants of them. Those are excellent and useful methodologies. They solve the problem of name clashing by prefixing all the classes.

The main issue with them is manual mangling: we have to write all these long selectors by ourselves. You can use preprocessors, but they only eliminate the effects, not the causes. The problem remains. Here is how we can use BEM to isolate a component (for other methodologies, the naming may differ, but the idea will be the same):

/* Plain CSS */
.article {
  font-size: 16px;
}

.article__title {
  font-size: 24px;
}

/* Using preprocessor */
.article {
  font-size: 16px;

  &__title {
    font-size: 24px;
  }
}

CSS Modules

2015 witnessed two approaches coming into existence. They are CSS-in-JS and CSS Modules. We’ll talk about the latter.

CSS Modules allow you to mangle all the CSS classes automatically, which makes them local by default. Then a JSON file is generated to store the mappings linking the original classes with the mangled ones:

/* post.css */
.article {
  font-size: 16px;
}

.title {
  font-weight: 24px;
}

The code above will be transformed into something like this:

.xkpka {
  font-size: 16px;
}

.xkpkb {
  font-size: 24px;
}

The mangled classes will be saved into a JSON object:

{
  "article": "xkpka",
  "title": "xkpkb"
}

After the transformations you can just read the resulting JSON object and use it instead of certain classes:

import styles from './post.json';

class Post extends React.Component {
  render() {
    return (
      <div className={ styles.article }>
        <div className={ styles.title }></div></div>
    );
  }
}

For more power, read this great post.

Not only the advantages of methodologies are preserved, but also isolated modules are mangled automatically. Sounds too good to be true, doesn’t it?

At this point, we’ve got another problem: we have tools to use CSS Modules at the client side only, but it’s not so easy to do it at the server side without using Node.js. At least, it was until recently.

PostCSS-modules

To have CSS Modules at both sides, I’ve written PostCSS-modules, a PostCSS plugin to use modular CSS at the server side with Ruby, PHP, Python or any other language.

PostCSS is a preprocessor that uses JS plugins to transform CSS styles. These plugins can lint your CSS, support variables, and mixins, transpile future CSS syntax, inline images, and more. For example, Autoprefixer is just a PostCSS plugin.

If you use Autoprefixer, then you already use PostCSS. Therefore, it shouldn’t be a big deal to add PostCSS-modules to your plugin list. I will show you how to do this using Gulp and EJS, but you can do the same with any other language.

// Gulpfile.js
var gulp         = require('gulp');
var postcss      = require('gulp-postcss');
var cssModules   = require('postcss-modules');
var ejs          = require('gulp-ejs');
var path         = require('path');
var fs           = require('fs');

function getJSONFromCssModules(cssFileName, json) {
  var cssName       = path.basename(cssFileName, '.css');
  var jsonFileName  = path.resolve('./build', cssName + '.json');
  fs.writeFileSync(jsonFileName, JSON.stringify(json));
}

function getClass(module, className) {
  var moduleFileName  = path.resolve('./build', module + '.json');
  var classNames      = fs.readFileSync(moduleFileName).toString();
  return JSON.parse(classNames)[className];
}

gulp.task('css', function() {
  return gulp.src('./css/post.css')
    .pipe(postcss([
      cssModules({ getJSON: getJSONFromCssModules }),
    ]))
    .pipe(gulp.dest('./build'));
});

gulp.task('html', ['css'], function() {
  return gulp.src('./html/index.ejs')
    .pipe(ejs({ className: getClass }, { ext: '.html' }))
    .pipe(gulp.dest('./build'));
});

gulp.task('default', ['html']);

Then we run these Gulp tasks and get transformed CSS and JSON files with mangled class names. Now we can use the values from the generated JSON file in our EJS template:

<article class="<%= className('post', 'article') %>">
  <h1 class="<%= className('post', 'title') %>">Title</h1>
  ...
</article>

If you want to see this code in action, check this example at GitHub. For more usage examples, see the PostCSS-modules repo and the original article about CSS Modules.

Never before writing maintainable CSS has been so easy. No more styles bloated with mixins. Long prefixes are history. Welcome to the world of tomorrow!

Join our email newsletter

Get all the new posts delivered directly to your inbox. Unsubscribe anytime.