Rafael Menezes

Code Quality with Prettier, ESLint and Husky

Today I’m gonna tell you a story.

“Once upon a time, you got hired by an startup to build an app in React from scratch. You setup the environment, setup the repository for the project, fire up your GitFlow and start developing. The company starts growing and hire another developer to help you out with the workload. She’s a nice person, really good developer, you guys get along pretty well but after the first merge you find out she has an evil side… she prefers spaces over tabs.”

Yeah, I know… it’s creepy.

“Anyway, after a good one hour discussing about which one is better, you both agree that for the project, one standard has to be established and you guys decided to go with tabs (of course).”

“One day you have to check out a branch your colleague is working on because she is sick and you realize that the issue with spaces is just the tip of the iceberg. She has a very different code styling like no spaces after commas, curly bracket in the next line, and the list goes on…”

Code differences is a normal thing that usually causes problems in a large, diversified team. But how do you avoid that?

Let me present you:

Prettier

https://prettier.io

Prettier formats code. It gets the a file and apply a set of rules on how your code should look and allows you to change a few things. It integrates with most of the popular editors out there and even has a nice feature, “format on save”. That’s perfect to solve the problem in the story above, right? Well, not completely.

Relying on the developer local environment configuration is not clever. What if somebody starts and doesn’t know about it? Or has to code from a different environment for a few days? Or a remote team mate joins and you don’t have control over any kind of configuration he uses?

We have to address the issue for everybody in the team. So let’s install and configure Prettier in a way that all the team will have access, even the new ones.

In the terminal run the following command in the root folder of your project

touch .prettierrc.js #creates a file
npm i -D prettier #same as 'install --save-dev'

The first line creates a Prettier configuration file for the project. This is good because even if some people use the editor extension, the rules in this file will be applied instead of the ones configured in the editor. The configuration supports .js, .json, .yaml and .toml files. You can even define the configuration in your package.json with a “prettier” key. I prefer the .js files because we can add comments

So open your .prettierrc.js file and add the following code

module.exports = {
  /**
   * printWidth: 80,
   * tabWidth: 2,
   * useTabs: false,
   * semi: true,
   * singleQuote: false,
   * jsxSingleQuote: false,
   * trailingComma: 'none',
   * bracketSpacing: true,
   * jsxBracketSameLine: false,
   * arrowParens: 'avoid',
   * rangeStart: 0,
   * rangeEnd: Infinity,
   * parser: undefined,
   * filepath: undefined,
   * requirePragma: false,
   * insertPragma: false,
   * proseWrap: 'preserve',
   * htmlWhitespaceSensitivity: 'css',
   * endOfLine: 'auto',
   */
}

These are the default values Prettier will apply to your code.

To run Prettier just execute

npx prettier --list-different "**/*.{js,jsx,css,json}"

This will show the files that don’t comply with the default rules. Let’s add a script to our package.json file so our team members can run it before committing the code:

"scripts":{
  "prettier": "prettier \"**/*.{js,jsx,css,json}\"",
  "format": "npm run prettier -- --write",
  "validate": "npm run prettier -- --list-different"
}

So now we have a way for our team members to format all the files according to a set of rules and everybody will have access to it. The --write flag apply the rules to the files. I also added a validate script that only checks without applying the rules, then we can run it on a CI tool before building the project and it will fail if it finds something wrong.

Awesome right? But one thing is well formatted code, another is well written code. Sometimes we forget old uncalled functions or variables, a typo that requires a long debugging to find or even a missing break in a switch that doesn’t break anything but causes undesired behavior (who never?)… many different issues not related to code format that Prettier won’t notice.

Then I present you another one:

ESLint

https://https://eslint.org

ESLint check for ‘lints’ in our code to save us time identifying potential problems during the development process. It comes with a huge set of configurable rules so you can define which ones are important to you, which issues you want to force a fix, which you want to suggest a change and which you want to ignore.

To add ESLint to our project just run

touch .eslintrc.js
npm i -D eslint babel-eslint

On our .eslintrc.js we need to add

module.exports = {
  parser: 'babel-eslint', // define babel as the parser
  extends: ['eslint:recommended'],
  parserOptions: {
    ecmaVersion: 2018, // understands let, const and other features
    sourceType: 'module', // understands the use of import and export
    ecmaFeatures: {
      jsx: true, // understands the use of tags inside js files
    },
  },
  env: {
    browser: true, // add browser globals variables like document and window
    es6: true, // add globals like Set
  },
}

Than we have to add the scripts to our package.json do the same as Prettier

"scripts":{
  "prettier": "prettier \"**/*.{js,jsx,css,json}\"",
  "format": "npm run prettier -- --write",
  "lint": "eslint src",  "lint:fix": "npm run lint -- --fix",  "validate": "npm run prettier -- --list-different && npm run lint"}

There is one confusing thing between them though, both Prettier and ESLint have rules related to format, when something conflicts what gonna happen? Very likely it will break the build. For the build to pass both Prettier and ESLint have to give a green check on the code but they disagree on how the code should be written that never gonna happen.

To address this issue we have to tell ESLint to ignore all the rules that Prettier is already taking care of. But which are them? As this is a very common issue, Prettier prepared a configuration file that we can pass to ESLint and it will override the rules from eslint:recommended. We just need to run

npm i -D eslint-config-prettier

And edit our .eslintrc.js file:

module.exports = {
  extends: ['eslint:recommended','prettier'],  parserOptions: {
...

Nice, but we still have problems with .jsx files because ESLint doesn’t have rules to deal with React code. Luckily somebody came up with a plugin that defines new rules for ESLint. Let’s add it, run

npm i -D eslint-plugin-react

And again, edit our .eslintrc.js file:

module.exports = {
  extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],  plugins: ['react'],  parserOptions: {
...
  rules: {    'react/prop-types': ['error',{skipUndeclared:true}]  }

So here we are telling ESLint that we want to define some new rules with plugins: ['react'] and that we want to use a predefined configuration for those rules using plugin:react/recommended. And in the end we define a rule to throw an error if we forget to check a prop using propTypes but only if the propTypes is declared.

So it’s all good now, everybody have the scripts to run and fix the code before committing it.

I’m quite sure that at this point you would like to ask: “But Raf, how can we be sure that somebody won’t forget to run the scripts?”

The honest answer is… we can’t.

Yeah… it sucks, but don’t be sad, I’m saying that we can’t make them remember, but I’m not saying that we can’t automatize the process. 😉

Git has one good feature called “hooks”. If you go to the terminal on your root folder and run ls .git/hooks you will see many .sample files. These files are bash scripts that you can modify and git will run during the process, be it a commit, a push, an update or something else.

So this is awesome, we have one that sounds promising for us, the pre-commit. We just have to find a way to make this script call our validate or format commands before the commit.

Nice, now we have to learn how to write a bash script? It’s not that hard but someone built a really good tool that can hook the scripts for us.

And this is when I present you:

Husky

https://github.com/typecode/husky

When you install Husky, it creates the bash scripts and they look for commands to run on our package.json file, we just have to tell Husky what do we want.

Let’s install it!

npm i -D husky lint-staged

And to configure it, add the following to your package.json:

"scripts":{
  "prettier": "prettier \"src/**/*.{js,jsx,css}\"",
  "format": "npm run prettier -- --write",
  "lint": "eslint src",
  "lint:fix": "npm run lint -- --fix",
  "validate": "npm run prettier -- --list-different && npm run lint",
},
"husky":{  "hooks":{    "pre-commit": "lint-staged"  }},"lint-staged":{  "**/*.{js,jsx,css}": ["npm run format","npm run lint:fix", "git add"]},

When we stage a file but before we commit it we go back to the editor and add more stuff, this new changes won’t be staged, we have to add them manually. This is the role of lint-staged. If you have 10 files but only stages 3 of them, lint-staged stashes the other 7, execute the commands on the other 3 staged files and unstash the other 7 back.

In our case we stage some files, make some changes to them using Prettier and ESLint, and stages these new changes to finish the commit process. If ESLint find errors that it can’t fix automatically, the commit fails.

Let’s see if the whole configuration works as we expect. Open one of your files and mess up with the formatting, remove all the indentation, add many empty lines and extra spaces between operators… go crazy on it. Now run the validate script to check, it should throw an error: npm run validate.

Add a “console.log” somewhere on your code to see the ESLint working (the recommended configuration defines console.log as a no-no).

Now let’s try it. Commit your code and see what happens:

git commit -am "testing prettier and eslint with husky"

You should get and error now saying:

| => git commit -am "testing prettier and eslint with husky"
husky > pre-commit (node v10.6.0)
  ✔ Stashing changes...
  ❯ Running linters...
    ❯ Running tasks for src/**/*.{js,jsx,css}
npm run formatnpm run lint:fix        git add
  ↓ Updating stash... [skipped]
    → Skipping stash update since some tasks exited with errors
  ✔ Restoring local changes...



✖ npm run lint:fix found some errors. Please fix them and try committing again.

> food-logger@1.0.0 lint:fix /Users/rafael/Development/react-apps/food-logger
> npm run lint -- --fix "/Users/rafael/Development/react-apps/food-logger/src/index.js"


> food-logger@1.0.0 lint /Users/rafael/Development/react-apps/food-logger
> eslint src "--fix" "/Users/rafael/Development/react-apps/food-logger/src/index.js"


/Users/rafael/Development/react-apps/food-logger/src/index.js
7:1  error  Unexpected console statement  no-console
✖ 1 problem (1 error, 0 warnings)

As you see, the Prettier fixed the format for us and the command passed (line 6) but ESLint found a rule violation (line 26) and thrown an error so the test failed (line 7)

If you remove your console.log command (or add a rule "no-console": "warn" to your .eslintrc.js file) and commit again, the test should pass.

*Remember the lint-staged only applies to staged files, so after fixing whatever you have to fix, add the changes to the stage before committing again.

Bonus

If you care about accessibility, there is a plugin for ESLint with rules to warn about potential accessibility issues in your jsx files, like declaring a image tag without the alt attribute.

To install the jsx-a11y plugin run

npm i -D eslint-plugin-jsx-a11y

It comes with to pre set configurations, plugin:jsx-a11y/recommended and plugin:jsx-a11y/strict that you can add to your .eslintrc.js:

...
extends: [
  "eslint:recommended",
  "plugin:react/recommended",
  "plugin:jsx-a11y/recommended",  "prettier"
],
...

If you set up your react environment using create-react-app, it pre installs eslint, eslint-plugin-react and jsx-a11y together with others. It also creates one configuration that set almost every rule to ‘warn’. If you want to use it, just add ‘react-app’ on your “extends” array (create-react-app does it by default).

And the last thing I wanted to talk about is, some standards have been established in the javascript community as good practices and many companies started using them. The two most famous are StandardJs and the Airbnb rules.

Have a nice code!