Linting styles in JavaScript with stylelint


I recently co-created a styling library for React called styled-components, which let’s you write actual CSS in your JavaScript. The issue is that you write this CSS inside strings and without selectors:

const Button = styled.button`
  background: tomato;
  border-radius: 3px;
  color: white;
  font-size: 1em;

  @media screen and (min-width: 750px) {
    font-size: 1.1em;
  }
`;

stylelint is a linter for CSS, meaning it helps you make sure you’re writing good code. It’s like eslint, except for CSS instead of JS. stylelint has a huge amount of rules and a vibrant ecosystem, which means it’s the best choice for CSS linting right now.

Leveraging the power and ecosystem of stylelint to allow developers to lint their CSS in styled-components seems like a great thing to do! How can we make that happen?

First try

The first idea was to leverage the power of eslint, the JavaScript linter, to parse the JavaScript and then run stylelint from there on the extracted CSS string. A lot of people already use and rely on eslint, so providing an eslint rule that does this would make it easy to adopt and use.

Sadly, that doesn’t work. After building a first prototype I noticed that for some unknown reason the stylelint results weren’t coming back to the CLI. I was executing stylelint on the right code, but the user couldn’t see the result! I asked around and somebody in the eslint Gitter chatroom explained to me why:

eslint rules have to be sync, but stylelint has to be run async.

Ugh, super annoying! I sent out a frustrated tweet on Twitter, to which thankfully @stylelint replied quickly:

@mxstbr @andreysitnik How about writing a stylelint processor that finds these strings and passes them to stylelint?

— stylelint (@stylelint) October 19, 2016

Huh, processors? Never heard of those…

Processors

As it turns out, stylelint has support for custom “processors”. Processors allow developers to add support for filetypes which stylelint normally doesn’t understand. They get the file contents, and have to return a CSS string which stylelint can lint.

One of the most common use cases would be linting CSS inside HTML (in <style> tags) or Markdown. (in code blocks) There are processors for that, which basically take the HTML/Markdown file you pass to stylelint, parse it, take the CSS code from the <style> tags or fenced code blocks, and pass it to stylelint.

That sounds exactly like what we need! How do I write one?!

Writing a processor

The main file of your processor has to export a function which returns an object with two keys, code and result:

// index.js

module.exports = function (options) {
  return {
    code: code,
    result: result,
  };
}

Both values of these properties have to be functions:

// index.js

module.exports = function (options) {
  return {
    code: function (input, filename) {
      return extractCSS(input);
    },
    result: function (stylelintResult, filename) {
      return fixSourceMap(stylelintResult);
    },
  };
}

In the code step, my styled-components processor parses the incoming JavaScript files with babylon. (the parser Babel uses under the hood) Then it gets the content of all the tagged template literals that are styled-components related and fixes the resulting CSS to be “real” CSS with selectors etc. before passing it to stylelint.

In the result step I then replace each mention of “brace” in all error messages with “backtick”, since you generally don’t have braces when writing styled-components, and adjust the source map to fit the JavaScript file again.

This is what that looks like now that it’s done 🎉

While it works, as you can imagine there’s quite a few edge cases and I definitely haven’t fixed all of them yet. Feel free to take a look at the source and if you use styled-components please try it out and let me know how it went!

To see what a really well tested, commented and perfectly working processor looks like take a look at the excellent stylelint-processor-markdown by @davidtheclark for some inspiration!


Liked this post? Sign up for the weekly newsletter!

Be the first to know when a new article is posted and get an inside scoop on the most interesting news and the freshest links of the week. (I hate spam just as much as you do, so no spam, ever. Promise.)


My one weird trick to be more productive 2016 in review: A dream come true