Ideally, any development project should have a Continuous Integration (CI) system that ensures the code will not break anything in the application and follows certain coding conventions.
This is what CI pipelines are for. An array of scripts needs to succeed before a pull request can be merged. One example of such a pipeline could be:
Install node modules
Run unit tests
Run static code analysis
Run build script
Now, imagine you’re finally done developing a feature and you create your pull request, not noticing you left out a console.debug() in the wild.
Say you did a good job and step 2 succeeds. However, your ESLint configuration disallows the use of console and step 3 fails. A big red icon shows up next to your pull request — crap, let’s hope nobody noticed!
Our pipeline did its job, but what if we could have caught this earlier? It would have saved us the hassle of fixing the code locally and update the PR. What about the build-minutes we just wasted for such a silly mistake? And more importantly, what about our PR reviewer(s) who already started to go through your code only to see the CI pipeline fail?
One way to avoid this situation is to perform all the pipeline checks locally, before creating the PR. But you can’t reasonably expect developers to do that before each commit. Automating this would be great right?
Git hooks are the answer
A git hook is nothing more than a script triggered when a specific event occurs in your repository. Hooks can run on the developer’s machine (client-side) as well as on the server hosting the repository (server-side). We will only scratch the surface of their potential here: if you want to learn more, here’s an excellent tutorial by Atlassian.
Writing git hooks comes with a few challenges, mainly to ensure they are installed on your teammates' machines.
1. Install husky
2. Enable git hooks
3. Automate git hook installation for your teammates
For npm, add this prepare script to your package.json:
Yarn requires a slightly different configuration:
Using Yarn2 in a non-private repo? Check this link.
4. Install lint-staged
Add the lint-staged configuration to your package.json and customise it to your needs. We assume that you already have linters and tests scripts set up.
5. Create your hook
This command will create a .husky folder at the root of your project:
What did we just do?
Let’s summarise what we set up so far:
Lint stage will be triggered each time we commit, thanks to husky.
It will only run our scripts on staged files, here specifically .ts and .tsx files.
Your linter and formatter will try to fix all the errors they find.
If these errors can be fixed automatically, lint-staged will add them to the current commit. If not, the commit will fail and output the error.
The prepare (npm) or postinstall (yarn) scripts we added before will ensure that these hooks are installed on your teammates' machines.
You will need at least Git 2.9, as husky uses the core.hooksPath configuration to point git hooks to the .husky folder.
Using git hooks allows you to catch your mistakes earlier and faster than when relying exclusively on CI pipelines. This helps you keep a clean git history, your colleagues happy and your build minutes consumption low.
Note that a simple —no-verify flag at the end of your commit command will skip the hook; client-side hook are not sufficient on their own and should be used together with CI pipelines.
Thanks for reading! 😃