Joshua Colvin

How to Set Up Git hooks with husky and lint-staged

Published September 04, 2020

Git hooks allow you to trigger actions at certain points in git’s execution. They are integral in keeping a codebase clean since we can do things like check code formatting, run linting, or run tests before code is committed or pushed to a remote repository. In this post, I’ll show you how to set up git hooks using a couple of helpful npm packages.

Prerequisites

Since we will be using npm packages you need to have Node and npm installed.

I will be using an Angular project generated with the Angular CLI. If you don’t have Angular CLI installed you can use the repository here as a starter.

Helpful npm Packages

We will rely on a couple of npm packages that will help us set up git hooks.

The first package is Husky which lets you tie an npm script or CLI command to a git hook in your package.json file. We will only be looking at implementing a pre-commit and pre-push hook, although Husky does support all the git hooks defined in the git documentation.

The second package is lint-staged that you can use to run linting against staged git files only. It’s helpful to run git hooks only on files that you have changed and are trying to commit or push. Running git hooks on all files in a large codebase would be prohibitively slow.

Installing Packages

We can install husky and lint-staged with the following command:

npm install husky lint-staged --save-dev

Note: husky version 4.2.5 and lint-staged version 10.2.13 were used at the time of writing.

Prettier

We will use Prettier, a code formatter, to test our pre-commit git hook. We can install prettier by running the following command:

npm install prettier --save-dev

In addition to the prettier package, we also need to add a configuration file where we can set our desired formatting options

Add a .prettierrc file to the root of your project and paste in the following config options:

.prettierrc
{
    "tabWidth": 4,
    "singleQuote": true,
    "printWidth": 120,
    "bracketSpacing": true,
}

Lastly, add an npm script that will run prettier with the --check flag. This will let us know if we have files that have not been formatted.

package.json
{
    "scripts": {
        ...
        "prettier:check": "prettier --config .prettierrc --check \"src/**/*.{ts,css,html}\""    }
}

Setting up git hooks in package.json

Let’s start by adding a pre-commit hook that checks that our code was formatted using Prettier. We want our commit to fail if files are found that are not formatted.

We can set up our git hook by adding a Husky config object to the package.json file and declaring what npm script we want to run for the pre-commit hook. We will run the prettier:check script we added earlier.

package.json
{
    ...
    "scripts": {
        ...
    },
    "husky": {        "hooks": {            "pre-commit": "npm run prettier:check"        }    }}

Try committing the changes you made to the package.json file. Your commit will fail with output similar to this:

Image of git log output
Image of git log output

You may have noticed that we made changes to the package.json file, but our commit is failing because the files in the src directory were not formatted. We only want to run the pre-commit hook on staged files since these are files that we edited. Doing this will keep our pre-commit hook checks fast.

Running git hooks only on staged files using lint-staged

We can use the lint-staged package to run our git hooks only on staged files. To do this, we add a lint-staged configuration object to our package.json file where we define what script we want to run on which files.

package.json
{
    "scripts": {
        ...
        "prettier:check": "prettier --config .prettierrc --check \"src/**/*.{ts,css,html}\""
    },
    "lint-staged": {        "src/**/*.{ts,css,html}": [            "npm run prettier:check"        ]    },    "husky": {
        ...
    }
}

Lastly, we need to update our husky pre-commit hook to run lint-staged instead of the prettier npm script:

package.json
{
  "lint-staged": {
    "src/**/*.{ts,css,html}": ["npm run prettier:check"]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"    }
  }
}

Try committing the package.json file again. It should work this time. Now, make a change to a file in the src directory like src/app/app.component.ts and try committing that file. The commit should fail since Prettier wasn’t used to format the file.

Running tests on pre-push git hook

It’s also a good idea to run unit tests before we push code to a remote repository. We can do this by setting up a pre-push git hook using Husky.

package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "pre-push": "npm run test"    }
  }
}

To test the pre-push hook we need to modify a unit test so that it fails. Open src/app/app.component.spec.ts and change line 16 from expect(app).toBeTruthy(); to expect(app).toBeFalsy();.

Now try pushing your code to a remote repository. The push will fail because you have a failing test. Revert the change you made to the test and try pushing again. The push should succeed now.

Conclusion

Using git hooks to check code quality is a great way to keep your codebase clean. I recommend that you use git hooks to check the formatting of all your code, run linting on your JavaScript/TypeScript and CSS/Sass, and run your tests. If we automate these checks we can be sure that they occur before code is committed or pushed.

Find this article helpful or interesting? Follow me on Twitter for related content.

Joshua Colvin is a UI Software Engineer specializing in building component libraries. He lives with his wife and two kids in Michigan.