Diving into Husky and Lint-staged
Tags: webdev, javascript, productivity, beginners
Published: 2021-05-12 14:47:52

---
title: Diving into Husky and Lint-staged
published: true
description: JavaScript tools are plentiful but manually running them doesn't provide the same value that workflow automation can.
tags: webdev, javascript, productivity, beginners
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k50mixkgzsbbgi4htrbj.jpg
canonical_url: https://laurieontech.com/posts/husky/
---


Last week I talked about [ESLint](https://laurieontech.com/posts/eslint/) and its usefulness for keeping projects consistent amongst multiple contributors. If you haven't read that post I recommend doing so before diving into this one.

Today, we're going to focus on running ESLint automatically to ensure that the main branch of your project always follows your specific ruleset.

## Lint-staged

The first tool to talk about is [lint-staged](https://www.npmjs.com/package/lint-staged). Lint-staged is configured in your package.json file.

```json
{
  "lint-staged": {
    "*.js": "eslint --fix"
  }
}
```

As seen in the above example, you can use a glob pattern to tell lint-staged which files to run against. Additionally, you can give lint-staged a command to execute against those files. In many cases, you'll want more than one command, which lint-staged supports. In this case, you'll run ESLint and [prettier](https://prettier.io/).

```json
{
  "lint-staged": {
    "*.js": ["eslint", "prettier --write"]
  }
}
```

So how does lint-staged work? It's specifically designed to work on "staged" files, thus the name. This means files you've changed or created but haven't yet committed to your project. Working on staged files limits the number of files you need to lint at any given time and makes the workflow faster. The commands you configure will run "pre-commit". As you're attempting to commit files to your project you'll see ESLint run in your terminal. Once it's done you may have successfully committed or find yourself with linting errors you need to fix before you're able to commit the code.

However, what you may not realize, is that lint-staged is not the only tool working under the hood. Lint-staged is designed to work with another tool called [husky](https://www.npmjs.com/package/husky).

## Husky

You may have come across husky before without noticing. For many years it was configured via a few lines of code in your package.json file. Something like this.

```json
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
```

However, the latest version of husky, v6, has changed this approach. Now, husky uses distinct bash files with filenames that match the workflow step they correspond to, e.g. "pre-commit". Luckily you don't have to set this up yourself and husky has a nice CLI command to do it for you.

```shell
npx husky-init && npm install
npx husky add .husky/pre-commit "npm test"
```

The first line of the command is a one-time initialization script that ensures all your coworkers will have husky installed on their machine before they try to commit files.

The second line creates the `pre-commit` file inside the `.husky` directory. If you look at the file you'll notice it's running a `husky.sh` script prior to whatever commands you initialized it with. This can technically be removed, but I'd recommend keeping it. The script allows for a few things, including the use of a `--no-verify` flag that bypasses the checks.

Once you've initialized the directory and associated file you can add whatever commands you want to it. In my case, I replaced `npm test` with `npm lint-staged`.

### Pre-push

The `pre-commit` workflow is more or less the husky happy path. But what if your project doesn't want to lint on commit and would prefer to lint when a developer attempts to push their changes to a branch?

While it's tempting to create a `.husky/pre-push` file and run lint-staged, it won't work. The `pre-push` husky workflow is correct, but running lint-staged at that point will turn up 0 matching files. This makes sense, though it certainly messed me up for a bit, because committed files are no longer staged. Instead, you have a couple of options.

1. Run ESLint against all the files: `eslint '*.js'`
2. Diff against `main`: `eslint --no-error-on-unmatched-pattern $(git diff main... --name-only --- '*.js')`

Note that this is one example of a diff command and there are numerous considerations depending on your project.

## Next steps and CI

Running ESLint, or prettier, or even tests as part of your git workflow is important because it helps you fail fast. However, it's not a replacement for CI checks. Typically, you'll want to run these commands in both environments to ensure nothing slips through.

But altogether these tools help ensure a cleaner, more consistent production codebase. Long term, that's a big win for any project.