Continuous CSS Improvement with Custom ESLint Rules

--

Since launching in 2013, Reverb has helped music makers of all levels connect with sellers all over the world and has grown into the largest online marketplace dedicated to buying and selling new, used, and vintage musical instruments.

As a fast-moving, growing business, Reverb has gone through a lot of iterations in our product as well as in our code. Take our CSS as an example. One of the most important elements of our application is ensuring we have a user interface that is enticing, easy to navigate, and allows buyers to find the information they need to make purchasing decisions.

The Problem: Handling Legacy CSS

Over the years, our CSS has gone through many iterations, from humble beginnings of hacking out bespoke styles on rails haml templates to our current design system that leverages a mix of BEM naming, utility classes, and reusable React components. While we try to make each iteration additive, one problem remains: what should we do with old CSS classes that don’t have any place in our newer, modern ecosystem?

Since we aim to ship and deploy incrementally and be as agile as possible, introducing new CSS schemes becomes a challenge. With new code being added, changed, and deployed all the time, it’s not possible for us to change all the CSS at once. Software development is often like upgrading a plane while it’s still flying — we can’t land the plane so we have to find ways to keep flying.

So, what should we do with old CSS classes that we don’t want to use anymore? Ideally, we could land the plane and just remove all references to them and delete them entirely. But in an interconnected application with thousands of lines of HTML and CSS, not to mention customers who expect us to keep upgrading the platform, that’s a non-trivial and time consuming task.

The next best thing we can do is to deprecate the old CSS classes, which means we want to stop any new code from going out that utilizes these classes. This recognizes that there is a big world of CSS classes that are still old, but it at least does not make the problem worse.

Let’s look at an example. Previously, if we wanted to style an alert component, we would have to apply a series of CSS classes. However, after creating a library of reusable React components, the developer no longer needs to know about these specific CSS styles and can instead just use the React component.

Here, we want to take the “Bad” code and make sure we’re only using the “Good” version moving forward. How do we make that happen?

ExampleBad:
<div className=”alert-box alert-box--red”>foo</div>
Good:
<AlertBox color=”red”>foo</AlertBox>

We could send a message telling every engineer to never use that CSS class again and only use the AlertBox React component — but what if they forget or don’t get the message? Maybe it gets caught in code review, but we can’t guarantee that nothing will slip through. And over time, as our component library grows, we’re going to keep adding to the list of core React components that replace old CSS utility classes. How can we maintain an up-to-date list?

Solution: Enter ESLint!

We already have ESLint (https://eslint.org/) set up to catch common code issues, so it’s a natural starting point. ESLint is a static code analyzer that will automatically run a set of rules and report any errors or warnings it finds.

For example, the following line of code would trip the missing semicolon rule and incorrect spacing after the > operator:

const booleanValue = (a >b) || c === d

Developers can have this set to run in their code editor. As we’re writing code, we can see if any of our code is breaking a rule. We also have configured ESLint to run as an automated build on every pull request, so any code that breaks a rule will not be allowed to be merged.

There are a bunch of incredibly helpful open source ESlint packages that we can and already have installed and configured for our project. However, this new requirement is something very specific to our application. To check for this, we need to create our own custom ESLint rule.

Creating a Custom ESLint plugin

ESLint is an incredibly powerful tool, and can be extended to suit any team’s needs. For our needs, we created an ESLint plugin directly within our monolithic repository that contains all frontend concerns. To do this, we first created an eslint-plugin-reverb-design-system directory.

Within that directory, we created a package.json file:

{
“name”: “eslint-plugin-reverb-design-system”,
“version”: “1.0.0”,
“main”: “index.js”
}

And an index.js file

module.exports = {
rules: {
‘prefer-components-to-classname’: require(‘./rules/prefer_components_to_classname’),
},
};

Then, we defined the rule in eslint-plugin-reverb-design-system/rules/prefer_components_to_classname.js:

From the top level of the project, we can then run yarn add --dev file:./eslint-plugin-reverb-design-system to add our newly created eslint-plugin to our project, and then we can add the rules to our eslintrc file.

Result: More Productive Engineers

Now, when you try to use a deprecated CSS class in your code, you get an incredibly helpful error message in the code editor (in this case VSCode) directing you to the correct thing to use instead.

This has been incredibly helpful in guiding the entire organization to write better code and to utilize our library of reusable React components, allowing all of our engineers to be more productive. Now, developers don’t have to keep a running list in their head of which classes are deprecated in favor of React components.

We plan to release similar rules like this in the future to further improve our development experience and code quality, and guide us to a more unified design system approach.

Interested in solving challenges like this one and making the world more musical? We’re hiring! Visit Reverb Careers to see open positions and learn more about our team, values, and benefits.

--

--