Getting started with Husky and Lint-staged for pre-commit hooks
Tags: git, webdev
Published: 2022-04-18 09:06:57

## Make sure each code commit is up to par and professional

When you’re working in a big codebase with many contributing developers, it can be difficult to make sure that all the code that is added conforms to a certain agreed standard. Coding standards can be defined as linters that identify stylistic and programming inconsistencies. Some companies and teams also include unit tests that have to be run for each commit to prevent new bugs to slip into the repository.

The agreed coding standards can be enforced by running linting and test scripts for each git-commit action. We ideally don’t want to bother developers to manually run this for every commit they make. To ensure that the rules are enforced, we need this to be automated. For efficiency, we also want the scripts to only be run on staged files — files with actual changes. Running these scripts on the whole repository is not necessary and can be even counter-productive as it can take much more time for each git commit to finish. There’s a big difference if one git commit takes a couple of seconds as opposed to a couple of minutes because the whole repository is being scanned.

### How to accomplish this check?
We need two tools to get the job done. The first tool that we need is a pre-commit tool that runs for every git-commit action: [Husky](https://typicode.github.io/husky/#/). The second tool that is needed is [lint-staged](https://github.com/okonet/lint-staged), which will run specified scripts on matching staged files. Aside from these tools, we need a code repository with actual linting tools. We will be using an Angular project as an example with the [Angular ESLint](https://github.com/angular-eslint/angular-eslint) and [Prettier](https://prettier.io/) pre-configured.

Tools that you will need to get started are:
- [Node.js](https://nodejs.org/en/) version 16
- IDE of choice (e.g. Visual Studio Code)
- [Angular starter project](https://github.com/duncanlew/demo-husky-lintstaged/tree/start-project) with ESLint and Prettier configured

This contains a link to the starter-project branch without any modifications for setting up husky and lint-staged.

<hr />

### 1. Setup Husky
Navigate to your project and run the following command to install and configure Husky for your project:
```bash
npx husky-init && npm install
```

This command will install Husky as a development dependency and create a simple pre-commit hook in `.husky/pre-commit` that you can edit. If we open this file, we can see that it has the following contents:
```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test
```

The pre-commit hook will run `npm test` by default for every git-commit action.

### 2. Setup Lint-staged
The next step is to install lint-staged. Lint-staged has been created with the purpose of linting only staged files. Lint-staged has very well-defined defaults that give us a headstart. One of the benefits is that Lint-staged will run the commands that you define for the chosen staged files and also immediately add the changes to the staged section if possible. Think of Prettier converting double quotes to single quotes for example. Lint-staged is also pre-configured to run the linting scripts on the staged files in parallel. This can be a huge time saver when there are many staged files. If you’d like to explore other configurable options, feel free to check out the [GitHub documentation](https://github.com/okonet/lint-staged).

Run the following command to install lint-staged:
```bash
npm install --save-dev lint-staged
```

Inside `.husky/pre-commit` replace `npm test` with `npx lint-staged`. Your file should look as follows:
```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
```

### 3. Configure Lint-staged
In the root directory of your project, create the file .lintstagedrc.json with the following contents:
{% gist https://gist.github.com/duncanlew/0c80fe2d771f79f46a2cce4444c31756 %}

This is the JSON configuration for running Prettier and ESLint for TypeScript, HTML and SCSS files.

The command `prettier --write` will run Prettier for the specified staged file and also prettifies the files in place according to Prettier rules. Lint-staged will also make sure that the edited files by Prettier will be added immediately to the staged section without any user interaction required. No `git add` is required afterwards!

The command `eslint` will lint the specified staged file and show any errors if the linting fails.

It may seem odd that there is no file argument specified since that is how you’d run Prettier and ESLint by providing this file argument. What lint-staged does in the background is translate `prettier --write` to a command with a file argument like: `prettier --write path/to/file.ts`.


### 4. Test drive
The proof is in the pudding. We want to see the pre-commit hook work in action and also block a commit if something isn’t up to par. We can do that by adding a console.log statement to any of the TypeScript files in the project:
```javascript
console.log('hello world');
```

Afterwards, we’ll execute the following in the terminal:
```bash
git add .
git commit -m 'hello world console log'
```

If Husky and Lint-staged are set up correctly, the pre-commit hook will be triggered and will show an error that the console.log statement isn’t allowed:
![Example output if a pre-commit hook throws an error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5wthh1cnova3o7wirft.gif)

We can see that the output from Lint-staged is actually pretty verbose. It tells you which types of files it was going to run the linting scripts for and also which script resulted in the failure.

### Takeaway
To sum up what we’ve done, every time we do a git-commit action, the Husky pre-commit gets triggered. Inside `.husky/pre-commit` we’ve defined that lint-staged gets run for every pre-commit action. Lint-staged will look for a `.lintstagedrc.json` file to know what scripts to run for each specified file type. The output of these scripts will be provided in the terminal by Lint-staged.

It is important to run ESLint, Prettier and other linting and testing scripts as part of your git workflow to ensure that each code commit conforms to an agreed standard. By integrating this into your git workflow with the pre-commit hooks, you can catch errors and inconsistencies very early on. This will ensure a much cleaner codebase in the long run. Make sure to tailor the steps in the article to accommodate your project. Think of extra linting scripts like [Gherkin lint](https://github.com/vsiakka/gherkin-lint) for feature files and [xml.js](https://github.com/kripken/xml.js) for XML files. Happy coding to you all! 👋

The complete source code can be found [here](https://github.com/duncanlew/demo-husky-lintstaged):

{% github https://github.com/duncanlew/demo-husky-lintstaged %}
 
If the content was helpful, feel free to support me [here](https://www.buymeacoffee.com/duncanlew):
<a href="https://www.buymeacoffee.com/duncanlew" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>