ESLint + Prettier For a Consistent React Codebase

.

ESLint + Prettier For a Consistent React Codebase

By Yonas Stephen

Different programmers have different styles of coding. One would put curly braces at the same line as function names, while others would write curly braces on a new line. Some tend to name variables in camelCase, others in PascalCase. There is no right or wrong. It’s only a matter of preference.

At GO-JEK, we always have at least two people for code pairing & reviewing a project. The moment two or more programmers put their hands on a project, you’ll start to notice inconsistency across your codes. This inconsistency bothers my engineering conscience.

P.S. In this post I’m going to assume we are building a React app with create-react-app, although most of the concepts here are applicable to any Javascript development.

ESLint to the rescue

Luckily we are not alone and that’s why Nicholas C. Zakas built ESLint.

Its goal is to provide a pluggable linting utility for JavaScript.

You can either install ESLint globally or vendor as project’s dependency (preferred). Let’s install it as a project’s development dependency:

npm install --save-dev eslint

Then you can run an interactive initialization command:

./node_modules/.bin/eslint — init

You’ll be asked to pick any base rules you wish to extend and config file format. In our case, we pick airbnb-eslint-config as base rules and JSON as config format. Voila! just like in Unix, a runcom fossil is generated as .eslintrc file in project’s root directory. Here’s how it looks,

Let’s try to decipher .eslintrc relics,

  1. env — By default, Airbnb config uses no-undef rule. If you use browser globals like localStorage or window in your code, these rules will cause linter to complain that those variables being never defined. Setting “browser” env key to true essentially tells linter, “Hey chill, I’m pretty sure these localStorage and window variables are defined by the browser. Don’t worry about it.” You can refer here for the list of supported env values.
  2. extends — These are the base rules you wish to extend.
  3. parser — This allows you to tune JavaScript transpiler and ES language features you like support. e.g. If you’re using ES6’s module systemimport React from 'react', you might bump into the keyword 'import' is reserved error. This is because ESLint doesn’t know about ES6 features yet. To fix this, you need to install babel-eslint npm i --save babel-eslint and set it as the parser.
  4. rules — I don’t really agree with all Airbnb’s base rules. If you feel the same way, you can override them here. For example, Airbnb base rule by default pick react/jsx-filename-extension rule and only allows JSX tag to appear in .jsx files. To us, it makes sense to have JSX tag in .js files, so we added .js to the allowed extension.

Once you have fine-tuned the rules as desired, you could run:

./node_modules/.bin/eslint --fix <your_directory>

TADAAA! Your code is all fixed now! Except… Maybe not. --fix turns out to only support few rules. As you can see from this page, there are not many wrench (fixable) icons.

Name a more iconic duo. ESLint + Prettier. I’ll wait.

Make it Prettier

Prettier is a code formatter that works differently from --fix under the hood.

Prettier takes your code and reprints it from scratch by taking the line length into account.

One example of what Prettier can do that ESLint’s --fix can’t do is:

// bad
foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne(), noWayYouGottaBeKiddingMe());

If you set max-len rule to 80, ESLint will just give you a warning that it’s too wide. If you use Prettier, it will format your code into this:

// good
foo(
  reallyLongArg(),
  omgSoManyParameters(),
  IShouldRefactorThis(),
  isThereSeriouslyAnotherOne(),
  noWayYouGottaBeKiddingMe()
);

Hopefully, you’re now convinced of using Prettier. It has few lint rules it supports on its own. But there are not many. Therefore, our goal is to now have Prettier do the code formatting based on the ESLint rules we define. Let’s start by installing these 3 libraries:

npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier
  1. prettier — the core prettier library. Duh.
  2. eslint-plugin-prettier — this plugin allows you to format code using Prettier when you run --fix . Say whaaaat.
  3. eslint-config-prettier — what happens if my ESLint rule says I need a comma at the end of every line, but my Prettier rule says that should never happen? This library solves just that. It turns off conflicting rules. ESLint’s rules, not Prettier’s. Obviously.

All you have to add plugin:prettier/recommended to the “extends” key in .eslintrc as such:

That addition does 3 things:

  1. Enables eslint-plugin-prettier.
  2. Sets the prettier/prettier rule to "error".
  3. Extends the eslint-config-prettier configuration.

Now run this command again:

./node_modules/.bin/eslint --fix <your_directory>

and you’ll have Prettier do the formatting for your ESLint rules 🚀

Taking it further

Asking programmers to run a command on every edit is not the most efficient way to enforce consistency. Here ate 3 alternatives you can follow to automate this:

1. Format on save with your favourite editor

Prettier has support for most popular editors such as Atom, VSCode, Sublime, WebStorm, and even Vim. You just need to get them installed, enable “format on save”, and enable ESLint integration.

But that’s super manual and time consuming to get it working with an existing dev setup.

2. Add lint stage in project’s CI/CD

You can add a script in your package.json as follows:

{
  "scripts": {
    "lint": "eslint src"
  }
}

Note: we don’t need ./node_modules/.bin when running using npm script because it will default to the project library, not the globally installed eslint (if any). Then in your CI config, add a lint stage:

This will cause project’s pipeline to fail, if the committed code does not pass lint rules.

Aha! now CI can report linting issues. No more, it works on my machine.

3. Pre-commit hook

You can use Prettier with a pre-commit tool. This can re-format your files that are marked as “staged” via git addbefore you commit. Prettier has the full list of options to achieve that; we are going with lint-staged and husky

npm install --save-dev lint-staged husky

Then add these scripts into package.json

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "src/**/*.{js,jsx,json,css,md}": ["eslint --fix src", "git add"]
  }
}

Now your lint --fix will be run every time you execute git commit and you can be assured that your codebase is consistent and well-formatted. That’s more like it.

don’t let 💩 slip into your codebase. — lint-staged

Final words

Linting can be one aspect people overlook in a codebase because more often than not, a project starts with a single person. Adding linter not only increases consistency, but helps detect bugs early and build maintainable code. The earlier we adopt, the better.

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live” ― John Woods

Any recommendations, tips? Would love to hear more.

If you’re interested in building beautiful, consistent, maintainable codebases, we are hiring Software Engineers! Check out gojek.jobs for more information.