How to add git hooks using husky v5 ( Bonus — Formatting + Linting + Lint Staged)

Aamir Saleem
5 min readMar 2, 2021
Git hooks with husky

Git Hooks

Git hooks are simple scripts that run before or after certain actions. They are useful for a variety of tasks like before committing format the code, fix the lint issues etc. Before push, run the test locally or create the build to check build compiles or not.

Husky

Husky is an npm package that “makes Git hooks easy”. You can use it to prevent bad git commit, git push, lint your commit messages, enforce code standards in your project and more. Husky supports all Git hooks.

Step 0: Initialize the project

For demonstration purpose, we need a starter project. Let’s use create-react-app to setup a react application. We are choosing react just to minimize the steps for getting set up to work with Husky.

npx create-react-app husky-app
cd husky-app

Step 1: Install husky and Enable git hooks

To install husky and enable git hooks, husky recommends the following(automatic) approach.

# npm
npm install husky --save-dev && npm exec husky init
# yarn
yarn add husky -D && yarn husky init

NOTE: Run git init first if the repository is not initialized.

The command above will setup husky and create a sample pre-commit hook that you can edit. Now your folder structure would look like this.

husky-app
├── .husky
│ ├── _
│ │ └── husky.sh
│ ├── .gitignore
│ └── pre-commit
....

Step 2: Add more hooks

To add another hook use husky add.

To lint commits before they are created we can use Husky’s commit-msg hook.
First, add commitlint cli and config-conventional to our project.

#npm
npm install -D @commitlint/cli @commitlint/config-conventional
#yarn
yarn add -D @commitlint/cli @commitlint/config-conventional

Lastly, we need to add rules to commitlint field in package.json

//package.json"devDependencies": {
....
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
}

Let’s add commit-msg hook:

# npm 
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'
# yarn
yarn husky add .husky/commit-msg "yarn commitlint --edit $1"

Let’s add one more hook. To prevent a push from taking place if script fails we can use pre-push hook:

#npm
npx husky add .husky/pre-push "npm run build"
#yarn
yarn husky add .husky/pre-push "yarn build"

Step 3: Test the hooks

Let’s commit the changes and see how husky fires off some of our scripts.

As you can see how husky fired off pre-commit script and how commitlint throw an error with text that does not follow the conventional commit pattern.

Now if we try to commit with a conventional message pattern our commit should be added.

Similarly, if we push those changes to the repository, we can see that the push process runs the pre-push hook which creates the build of our project.

NOTE: Before push remote origin should exist.

Bonus Step: Use Husky to format and lint the code

First, let’s talk about ESlint. It is a static code analyzer, which means it tells you errors and mistakes that you may make while you are developing.

Prettier is a code formatter, it’s only concerned with how your code looks to ensure consistent indentation in the entire project.

Let’s install the dev dependencies for eslint and prettier.

yarn add -D eslint prettier \
eslint-plugin-react eslint-plugin-react-hooks \
eslint-plugin-prettier eslint-config-prettier \
eslint-plugin-jsx-a11y

Making the config files

touch .eslintrc.json .prettierrc

Let’s populate our configs now.

// .prettierrc
{
"semi": true,
"tabWidth": 4,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none",
"jsxBracketSameLine": true
}
// .eslintrc.json
{
"root": true,
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"react": {
"version": "detect"
}
},
"env": {
"browser": true,
"amd": true,
"node": false
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": ["error", {}, { "usePrettierrc": true }],
"react/react-in-jsx-scope": "off",
"jsx-a11y/anchor-is-valid": [
"error",
{
"components": ["Link"],
"specialLink": ["hrefLeft", "hrefRight"],
"aspects": ["invalidHref", "preferButton"]
}
]
}
}

We need to add scripts to our package.json and removeeslintConfigfrom package.json because create-react-app add it by default.

"scripts": {
"lint": "eslint --fix . --ignore-path ./.gitignore",
"format": "prettier --write './**/*.{js,jsx,css,md,json}' --config ./.prettierrc --ignore-path ./.gitignore"
},

Now simply add these scripts to the pre-commit hook

// pre-commit#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn format
yarn lint

Let’s test by commit the changes.

Thanks to the husky pre-commit hook our code got formatted and exited with error code 1 due to the linting issues. I am not going to fix the linting issues in this article but hopefully you got the idea.

Lint-Staged

Running linting/formatting scripts on a whole project is slow and linting results can be irrelevant. Ultimately you only want to run the scripts against the files that will be committed. Lint-staged allows you to granularly pick the files that will go into your next commit i.e staged files.

The installation and setup are straightforward:

#npm
npm install --save-dev lint-staged
#yarn
yarn add -D lint-staged

and add a list of commands with glob patterns to run these commands against:

"lint-staged": {
"./**/*.{js,jsx}": [
"yarn format",
"yarn lint"
]
}

Add lint-staged to package.json scripts and modify pre-commit hook to run the lint-staged script.

"scripts" {
...
"lint-staged": "lint-staged"
}
// pre-commit#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged

Conclusion

That’s it. Thank you for reading this far. You can find the full source code at Github. Let me know if you find some issues. I have taken a lot of help from the following articles.

--

--