How to Set Up Git Hooks in an Nx Repo

Share on facebook
Share on google
Share on twitter
Share on linkedin

Git hooks can be used to automate tasks in your development workflow. The earlier a bug is discovered, the cheaper it is to fix (and the less impact it has). Therefore it can be helpful to run tasks such as linting, formatting, and tests when you are e.g. committing and pushing your code, so any problems can be caught as early as possible.

This post will cover how to get an efficient development workflow using Git hooks that runs the necessary tasks at the correct times in your development workflow, all automatically.

TLDR:
We will use Husky to execute tasks for these hooks:

commit-msg: Ensuring our commit messages are following conventional commits format.
pre-commit: Run linting and formatting on staged files using lint-staged with eslint, style-lint and nx format:write.
pre-push: Run unit and component tests on affected projects.

Installing Husky

We are using Husky to enable us to run node scripts on Git hooks.

We install and init Husky with:

npx husky-init && npm install

This will also add a prepare script in the package.json:

"prepare": "husky install",

commit-msg

It’s essential to have your team agree on a format for writing commit messages. Especially if you are working on a library and you want to generate a CHANGELOG.md file that shows the latest changes. Commitlint allows you to check the if commit message follows a specific convention (such as conventional commits like those used in the Angular repo).

Setting up commitlint

We first install commitlint as:

npm install --save-dev @commitlint/{config-conventional,cli}

Then we configure it to use conventional commits:

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

We can now hook this into the commit-msg git hooks with Husky by creating a hook inside the .husky folder:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

We use npx so we don’t need to have dependencies like commitlint installed globally but can use the local package.

This will generate a file looking like this:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit "$1"

And now it will give us an error if we try to make a commit with a commit message that doesn’t follow the commit convention:

pre-commit

For pre-commit we want to lint and format the repo, so every developer follows the same coding guidelines and formatting rules automatically. It’s important to run this as a git hook and not just rely on the CI catching this as that would lead to extra commits (eg. fix: lint), slower development due to a slower feedback cycle, and wasted CI agent resources.

With Nx we can use nx lint to lint the files based on our eslint configuration, nx format:write for formatting (uses prettier under the hood) and stylelint to lint scss files and combine it with lint-staged to only run lint on the staged files about to be committed.

Setting up lint-staged

We first need to set up lint-staged:

npm install --save-dev lint-staged

Let’s go ahead and use lint-staged to run lint and formatting before committing.

Running lint and formatting

An Nx project automatically sets up prettier and eslint which are used under the hood correspondingly for the commands nx format and nx lint so we don’t need to cover the specific setup for that here.

We do need to configure stylelint first.

Setting up stylelint

First we set up stylelint with:

npm init stylelint

And we set up a basic stylelint config that supports scss by creating a .stylelintrc.json containing:

{
	"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
	"plugins": ["stylelint-scss"],
	"rules": {
	}
}

You can override the default rules as needed in the rules object.

Setting up the hook

We create a .lintstagedrc to run commands on staged files:

{
    "*.ts": [
	"nx affected:lint --fix --files"
    ],
    "*": [
        "npx nx format:write --files"
    ],
    "*.scss": [
	"npx stylelint --fix"
    ]
}

We make a pre-commit hook that triggers stage-lint:

npx husky add .husky/pre-commit ‘npx lint-staged –relative’

This gives us:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged --relative

We use --relative so lint-staged will apply the paths of the staged files as relative paths which we need for nx format:write.

So now we will have eslint, formatting and stylelint running when we commit.

pre-push

Running tests can be a rather lengthy job especially if you both are running unit and component tests (which is what I recommend). So we want to only run this before we push a branch so we still get fast feedback if a test is failing and can fix it with a git amend commit in case.

npx husky add .husky/pre-push 'npx nx affected -t test && npx nx affected -t component-test'

This will both run test and component-test for any affected project on pre-push.

Github repo

Conclusion

In this post, we learned how we can make our development workflow more efficient and streamlined using Husky for hooking our tools into our Git workflow.

This is part of the training for becoming an Angular architect so if you are interested in learning more about this, check out the next cohort of Angular Architect Accelerator.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

Error, loading, content…? Use this page pattern for your Angular apps

When developing Angular applications, it’s common for pages to transition through three key states: error, loading, and show content. Every time you fetch data from an API, your page will likely show a loading indicator first, and then either render the content successfully or display an error message if something goes wrong. This pattern is

Read More »

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »