DEV Community

Volodymyr Sinievych
Volodymyr Sinievych

Posted on

7 front-end projects with a single universal code style config: here is how

Hi👋
My name is Volodymyr, I am a software developer at Livebeam by SocialTech, and I am here to share some of our experience.
Today, I offer a high-level overview of how we put ESLint in place and scaled it across our multi-repository front-end project. If you’ve been planning to improve code consistency in your project – this article might just be the place to begin.


Front-end developers are familiar with ESLint, a great tool that helps maintain code quality and catch bugs. It is a fairly straightforward utility when used in small projects. All it takes to get started is adding the dependency and going through the basic setup stages.

However, what if a project is already in place, and that project desperately needs linting? What if that project consists of a few repos, or even a few dozen repos, maintained by several teams? Scaling ESLint across multiple repos has more to offer than just repeating the same steps for each of those.

First, I will take you on a history tour of how we standardized linting practices across multiple repositories. Then I will share our experience on how to coexist with all the warnings and errors. Strict ESLint rules bring just so many of them into the life of a mature project.

How it all began

Let's take a look at our starting point. We are an 8-year-old project and have been keen on adding new features (our excuse for everything that's wrong with our codebase, lack of code style, for one). We work with multiple front-end repos – there's a general product UI, a chat microservice, some internal use products such as a content moderation platform, a payments repo, and a few more. And though we had code-style agreements, and our repositories all used ESLint, we were not entirely happy with how things functioned. The reason was different developers were working on separate repos, so somehow, they all ended up with different configs.

Initially, each part of the project started with a strict albeit reasonable ESLint configuration. As time passed, decisions were made to switch some errors to warnings. Later, someone took the liberty of disabling those warnings altogether because they…well, it is pretty irritating when you are prompted about errors beyond your current work scope, and while you would love to get rid of those, you can't.

What is wrong with the absence of a uniform setting?

Do not get me wrong; it is okay for a dev team to configure linting rules to their liking. Yet it is quite problematic when teams' tasks intersect, and you have to peek into each other's repos, and it feels like traveling to a different planet.

The reason why we want the same linting rules across all our front-end code is uniformity. To achieve that, we need a single source of truth, which in our case, is an ESLint library we developed that all our projects share.

We opted for this method since rules are meant to remain unchanged for a long time. Making changes to a global config stored externally is quite a lot of work, and we hoped this added complexity would discourage us from changing the config rules. In this way, we prompt each other as developers to change our code which does not meet the standard, not reform the standard itself.

The benefit we aimed to witness was the ease of code perception. Good code reads like well-written prose (as Robert Martin says), and it is especially so when the code you read meets your expectations in terms of formatting. Spacings, semicolons, and code blocks – all affect how the code reads. It is easier to perceive code that is written uniformly. You do not feel that code is alien to you – it always feels a bit more like home, regardless of the author.


Now, I remember what we were thinking at that point: will we have some heated arguments on how to format our code. We did. One was indentation: 2 spaces or 4 spaces?

Here is a fun fact, the longer the tabs you have, the faster you run out of horizontal space (provided max-len rule is enabled).

If you got 4 spaces, indenting 4 levels means 16 spaces are used up at the lowest level, twice as many as with 2 spaces. The line length restriction will give a hint when you get too deep inside a class or a function. When that happens, it is probably time to take a step back and reconsider your current practices – extract a new function, decompose, use fewer arguments, remove nested if statements, or whatever that takes and, ultimately, will result in cleaner code.


So yeah, it took a lot of time to delete the configurations stored locally and switch to the one in a library. Our rule knobs were pretty much turned to 0. We had 4 teams working on the project doing tons of business tasks. Some of those tasks made small and local changes, while others shaped the product. The thing was, it was not possible to just start turning rules on one by one. There was a CI/CD platform lint check in place, too, so anything marked as 'error' in your code would render it unsuitable for delivery.

As we embarked on this journey, we needed requirements.

We decided to

  • Agree upon 'good' rules we all would be happy to follow.
  • Do not block development – that means no part of the product can be in an 'undergoing repair' state for more than a day.
  • Do not stop development – we can't just take a few weeks off to rewrite everything. Our company needs us.

So these were the steps we took:

First, set rules in place and check how well the auto-fix works. Basically, we sat together and compared two pages of code against one another, and someone would say, 'this piece looks terrific' or' let's not use one-liners.' Keep in mind that some rules will be easy to agree on, but some not so much. Willingness to compromise is appreciated.

Next, find a day when as few pull requests are open as possible and run an auto-fix on a single rule. Just run it, merge it, and you're done. When it comes to code style nuances, more often than not: if it builds – it works. Remember that whoever had their pull requests open at that point will end up with a gazillion merge conflicts. This approach worked well for us, so iteration by iteration, we managed to reach a number of 900. That many errors were not auto-fixable. That caught us slightly off guard as we were clueless about what to do next.

The first idea that crosses one's mind at this point is to roll up their sleeves and go fix it all. That very idea visited our naive minds, and we got to work. To our regret, such burning enthusiasm faded as we realized quite a batch of those errors came up in the code that none of us took part in writing. Little did we know how it worked or why it worked. An extensive system of tests could have remedied this, but we did not have one in place at the time. You can take risks and refactor such code pieces, provided they were there to stay for a long time. However, as a changing, evolving product (and whose isn't?), we can never rest assured we are not dropping certain functionality in a month or rewriting the logic behind it completely. Because of that, putting great effort into refactoring such code was risky.

All in all, we decided to stick with the boy scout rule: always leave the code you're editing a little better than you found it. We used a handy plugin called suppress ESLint errors which added disable-next-line exceptions to those errors we were left with, and later, when someone made changes to a file, they could take time to fix it and delete those comments. Rules left by the plugin are prefixed, so they are different from manually disabled rules, which helped us set our priorities.

Code comment says TODO: fix me later if you can, followed by a rule disabling comment

The first line says, 'This rule was disabled automatically. If it seems reasonable, refactor me to meet our code-style agreements. If you meet me during code review, remember it is voluntary to fix me.'

Only a rule disabling comment is present

In this snippet, we see an example of a deliberately disabled rule. This one says, ‘Pay closer attention to me during review.’

Finally, let's recap the flow once again:

  1. Gather your developers and agree on the code style you all don’t mind following
  2. Configure the ESLint external library (if there is more than one repository you work on).
  3. Run auto-fix on simple rules one by one, ensuring the project keeps building and all your tests pass (you do have tests, right?).
  4. Use an autoprefixer plugin to disable the remaining rules locally and mark them ‘auto disabled.’ This will allow you to use ESLint as a pre-commit hook locally and in your CI/CD platform and not get errors but still keep track of your problematic areas.
  5. Whenever you get to code, try fixing those prefixed exceptions in the files you work with. During code review, praise your colleagues for doing that to keep them motivated ;)

There is also an alternative approach. Follow steps 1-2 from above, but

  1. Set all rules to a ‘warning’ level.
  2. Pick a single rule, and set it to an ‘error’ level.
  3. Get rid of all the errors by letting ESLint enforce the style automatically or by manually refactoring the code. If neither option is possible - disable the rule with the disable-next-line comment, and add a ‘fix-me-later-if-you-can’ comment, the same way we used the autoprefixer plugin to do it for us.
  4. Repeat increasing the number of ‘error’ rules and decreasing the number of ‘warning’ rules until you are done.

This is definitely easier said than done, and it took us a whole year to follow through with this plan.
If that sounds interesting, we may follow this up with a hands-on article on how to set up your npm package library that stores ESLint config.

Top comments (2)

Collapse
 
larisaantsifrova profile image
Larisa

Thank you for sharing your experience with standardising code style practices throughout several micro-frontends!

Have you as a team thought of monorepos? Or this architecture is not suitable for your project? :)

Collapse
 
volodymyrsinievych profile image
Volodymyr Sinievych

Hi there 👋
I'm so glad you've enjoyed reading the article!

We've been looking into that for some time. Redesigning our architecture for better ESLint support would probably feel like cracking a nut with a a sledgehammer :)) I guess, we would need a better reason than that to okay such a transition. Once enough good reasons pile up - we might have to do just that!