May 12, 2020

Using Eslint and Prettier with Husky and LintStaged

Gain more confidence in your code and commits!

As software developers we spend more than 70% of our time just staring at code to figure out what it's doing or just trying to find stupid bugs that have made their way into our code. Let me show you a bug that I've actually seen in production

if (typeof thing === number) {
  // Do something here
}

At first glance, it may not be obvious what the problem is. Have you figured it out? Let me give you a hint, typeof operator returns a string, so it should be

if (typeof thing === "number") {
  // Do something here
}

Debugging can be very time consuming and hard. That's where ESLint comes to our resuce. Using ESLint can help us catch these bugs and save countless hours of debugging. Not only ESLint helps us catch bugs, it can also be used to enforce a consistent code style across the code base. Enforcing a consistent code styles makes sure everyone in the team is one the same page and the code becomes more predictable, which ultimately means more stability.

Where does Prettier come into the picture then? Prettier is a tool that helps us format code automatically. Having a consistent formatting across the code base means less time arguing over tabs versus spaces in the commit reviews and more time focussing on the code that actually matters.

Let's start by creating a new project and see how to actually use these tools

mkdir linting-demo && code .

This will create new directory called linting-demo and open it in VS Code. Now run

npm init -y

Let's write a lowly add function

const add = (num1, num2) => {
  return num1 + num2;
};

Let's intall eslint

npm i -D eslint

After ESLint is installed we need to create a config file for ESLint. Create .eslintrc file in the root of the project. Let's start by putting an empty config. Our .eslintrc file should look like this

{}

To see the visual feed back right away, install the ESLint extension for VS Code. And put the following lines in VS Code settings.json

 "eslint.run": "onType",
 "eslint.enable": true,

We want to enable ESLint and want to run it as soon as we type something. Let's see how the feedback looks like!

Empty Eslint Config

So far nothing seems to be wrong with our code, so why do we have a red line under const? The fact is that by default ESLint expects our code to be ES5. Let's fix this. Add the following lines to .eslintrc

{
  "parserOptions": {
    "ecmaVersion": 2019
  },
  "env": {
    "node": true
  }
}

Let's add a few rules and see how our code looks after that. Your .eslintrc shoud look like this

{
  "parserOptions": {
    "ecmaVersion": 2019
  },
  "env": {
    "node": true
  },
  "rules": {
    "camelcase": "error",
    "semi": "warn",
    "no-console": "warn"
  }
}

And this is how our code looks like.

Code Screen Shot

You can customize as many rules as you want and make it to your liking but that's a hassle and we don't want to do that. Eslint can be extended with cofigs that other people have already written. Let's add a config that comes with eslint out of the box eslint:recommended. If you're working with React eslint-config-react-app is a very good one. For now let's use the recommended config. Edit your .eslintrc file

{
  "parserOptions": {
    "ecmaVersion": 2019
  },
  "env": {
    "node": true
  },
  "extends": ["eslint:recommended"],
  "rules": {
    "no-console": "warn"
  }
}

Not everybody is going to be using VS Code and they may not have their editor set up to have visual feed back which VS Code extension eslint provides. So Let's add a lint script. In your package.json, add the lint script. Your package.json should look like this

{
  "name": "linting-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "lint": "eslint --ignore-path .gitignore ./src"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^7.0.0"
  }
}

The --ignore-path .gitignore flag tells eslint to ignore everything that's in .gitignore . Since we are not going to commit them, it makes no sense to lint them anyway. Let's run the lint script and see the output.

output of lint

As expected eslint shows us an error that the function add is defined but never used as this violates the rule no-unused-vars. We can of course change the error to a warning but that's upto you. I'm going to leave it as it is.

Let's talk about Prettier now. Let's install Prettier.

npm i -D prettier

Also let's setup our VS Code to format our files when we save them. Install the prettier extension for VS Code then open settings.json of VS Code and add the following lines.

 "editor.defaultFormatter": "esbenp.prettier-vscode",
 "editor.formatOnSave":true,
 "prettier.requireConfig":true,

We are setting Prettier to be our default formatter. Also prettier.requireConfig:true tells VS Code to format the code only if there's a .prettierrc file available in the project. That's just how I like it. You can omit this line and even when the config file isn't present, Prettier will format your code using it's default settings. You can also add prettier rules in VS Code setting.json as well.

Let's add a .prettierrc file to the root of our project.

{
  "arrowParens": "avoid",
  "semi": true,
  "singleQuote": false,
  "trailingComma": "none",
  "printWidth": 80,
  "bracketSpacing": false
}

These are a few rules that I like. You can of course tweek them to your liking. Let's add a format script. Our package.json should look something like this

{
  "name": "linting-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "lint": "eslint --ignore-path .gitignore ./src",
    "format": "prettier --write --ignore-path .gitignore \"**/*.+(js|json|jsx|md|mdx)\""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^7.0.0",
    "prettier": "^2.0.5"
  }
}

We are telling Prettier to format any files that end with these extensiobjectons .js, .json, .jsx, .md, .mdx. Again, we are excluding everything that's inside .gitignore.

The way we want to setup ESLint and Prettier is, that we want to let ESLint check our code for errors and let Prettier handle the formatting. But the problem is that sometimes these two tools can have conflicting rules. To fix this, we are going to install an ESLint Config

npm i -D eslint-config-prettier

After we've installed this, let's tweek the extends property in .eslintrc. It should be like

{
  "parserOptions": {
    "ecmaVersion": 2019
  },
  "env": {
    "node": true
  },
  "extends": ["eslint:recommended", "eslint-config-prettier"],
  "rules": {
    "no-console": "warn"
  }
}

You can add as many configs as you like, just make sure that eslint-config-prettier is the last one in the array.

Now that we have both ESLint and Prettier setup, let's add Husky into the mix. What we want is to run our lint and format script when we are about to commit. Husky is a tool that let's us use git hooks in a much easier way. Let's setup a pre-commit hook with Husky to run our scripts.

npm i -D husky

Let's create a .huskyrc.json file in the root of our project and add our pre-commit hook.

 {
  "hooks": {
    "pre-commit": "validate"
  }
}

Wait, but we don't have a validate script yet. Let's create and add it into our package.json

"validate": "npm run format && npm run lint"

Let's add some buggy code into our index.js and try to commit it.

const add = (num1, num2) => {
  return num1 + num2;
};
let x = 3;
// y is not defined
const sum = add(x, y);
console.log(sum);

Let's run the following commands

git add .
git commit -m "Code has some errors"

Here's what the terminal looks like when we try to commit this code

commit hook failed

As you can see on the bottom of the above screen shot that our pre-commit hook failed and the code was not comitted. If we have to commit the code anyways, we can always use the no-verify flag. Pretty nifty, right?

That's great, but there's one small problem, we are running the lint and format script on the entire project. Which can take a lot of time. What we want to do is to only lint the staged files when we are about to commit. That's where LintStaged comes in handy. Let's add that to the project

npm i -D lint-staged

Let's create a .lintstagedrc.json file in the root of our project.

{
  "*.+(js|jsx)": ["eslint"],
  "**/*.+(js|json)": ["prettier --write"]
}

We want to run ESLint on all our JavaScript files and also format them. Let's change our .huskyrc.json file to run LintStaged on the pre-commit hook. Make sure your .huskyrc.json looks like this

{
  "hooks": {
    "pre-commit": "lint-staged"
  }
}

That's it. We are pretty much done. Let's fix our code, make a commit and check the output. linstaged

There we go, we're all done. Now we can have more confidence in commits and pushes.

Tags
ESLintPrettierReactJavaSctipt
Adnan Khan's face
Adnan Khan is a Full Stack web developer who specializes in JavaScript, React and Nodejs.