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.