Getting started with Husky and Lint-staged for pre-commit hooks
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. The second tool that is needed is 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 and Prettier pre-configured.
Tools that you will need to get started are:
- Node.js version 16
- IDE of choice (e.g. Visual Studio Code)
- Angular starter 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.
1. Install Husky
Navigate to your project and run the following command to install and configure Husky for your project:
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:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test
The pre-commit hook will run npm test
by default for every git-commit action.
2. Install 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.
Run the following command to install lint-staged:
npm install --save-dev lint-staged
Inside .husky/pre-commit
replace npm test
with npx lint-staged
. Your file should look as follows:
#!/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:
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:
console.log('hello world');
Afterwards, we’ll execute the following in the terminal:
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:
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 for feature files and xml.js for XML files. Happy coding to you all! 👋
The complete source code can be found here.
If the content was helpful, feel free to support me here: