Our goals
- Auto-format source code before each commit
- Fast local tests before each commit
- Auto-install npm dependencies when switching branches
The base: Git hooks with Husky
Husky is a thin wrapper around git hooks - shell scripts that execute in response to git actions.
Husky configures git to use the .husky folder for reading hooks.
Setup is straightforward, install husky
β  npm install husky is-ci --save-dev
β  npx husky installand setup the husky install command to run automatically outside of CI environments
// package.json
{
  "scripts": {
    "prepare": "is-ci || husky install"
  }
}And finally, add a test pre-commit hook:
β  npx husky add .husky/pre-commit 'echo "the pre-commit hook ran π"'This will put our script in the .husky directory:
β  test-repo git:(master) β tree .husky
.husky
βββ _
β   βββ husky.sh
βββ pre-commit.husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "the pre-commit hook ran π"And we can test that our hook executes when we commit:
β  git add .husky && git commit -m 'add husky'
the pre-commit hook ran π
 ....1. Auto-format source code before each commit
Using prettier and pretty-quick auto-formatting code is as simple as:
β  npm install prettier pretty-quick --save-devUpdate .husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx pretty-quick --staged # β
 replace the dummy "echo" commandAnd weβre done, now our files will be auto-formatted by prettier before each commit!
β  git add . && git commit -m 'add pretty-quick'
π  Finding changed files since git revision ff5738c.
π―  Found 2 changed files.
βοΈ  Fixing up package.json.
β
  Everything is awesome!
 ....2. Fast local tests before each commit
We will use lint-staged to execute minimal local tests before each commit.
The following setup will only execute tests related to the files that have actually been changed in the current commit instead of your full test suite. We can leave the full test suite execution to our CI.
β  npm install lint-staged jest --save-devUpdate .husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx pretty-quick --staged
npx lint-staged # β
 add lint-stagedAdd .lintstagedrc.js
// .lintstagedrc.js
module.exports = {
  // If any ts/js(x) files changed.
  "**/*.{ts,js}?x": [
    // Execute tests related to the staged files.
    "npm run test -- --passWithNoTests --bail --findRelatedTests",
    // Run the typechecker.
    // Anonymous function means: "Do not pass args to the command."
    () => "tsc --noEmit",
  ],
}3. Auto-install npm dependencies when switching branches
A great way to lose time chasing non-existent bugs is to forget that different branches can have different dependencies in package.json.
The following setup will automatically run npm install after you switch branches or execute git commands that can modify package.json, e.g. a merge.
β  npx husky add .husky/post-checkout 'echo "the post-checkout hook ran π"'Update .husky/post-checkout:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# From the post-checkout docs https://git-scm.com/docs/githooks#_post_checkout
# The hook is given three parameters:
#  - the ref of the previous HEAD
#  - the ref of the new HEAD (which may or may not have changed)
#  - a flag indicating whether the checkout was:
#     - a branch checkout (changing branches, flag=1)
#     - a file checkout (retrieving a file from the index, flag=0).
# When the third script parameter is "1" we are executing a branch checkout
if [ "$3" = "1" ]; then
  # install npm packages
  npm install
  # or with yarn
  # yarn install --frozen-lockfile
fiAdditionally, we should run npm install after a merge or a rewrite.
We can setup those up with just 2 commands:
β  npx husky add .husky/post-merge 'npm ci'
β  npx husky add .husky/post-rewrite 'npm install'Or with yarn:
β  npx husky add .husky/post-merge 'rm -rf node_modules && yarn install --frozen-lockfile'
β  npx husky add .husky/post-rewrite 'yarn install'