Create a pre-commit hook

A pre-commit hook preserves the quality of your code by running lint, format, and/or test scripts before commits. It won’t allow you to commit the code if it finds a problem. A common tool that is used for setting up pre-commit hooks is husky. Husky, is often used along with lint-staged. Lint-staged improves the performance of your pre-commit hooks by allowing you to run scripts only against the staged files.

Install dependencies

Let’s see how you can create a pre-commit hook with husky and lint-staged. First, install the dependencies:

yarn add -D husky lint-staged

Configure husky

Then, configure husky to run the lint-staged script before you commit the code:

// package.json
{
  "name": "pre-commit-hook",
  "private": true,
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "scripts": {},
  "husky": {    "hooks": {      "pre-commit": ["lint-staged"]    }  }}

By the way, you can create more git hooks with husky; you are not restricted to pre-commit hooks.

Linting

You now have to configure lint-staged. The following script runs ESLint against the staged files, and as a result, will fail—it won’t commit the code— if there is a lint error:

// package.json
{
  "name": "pre-commit-hook",
  // more stuff..
  "scripts": {
    "lint": "eslint --ext js,jsx src"  },
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {    "*.{js,jsx}": ["eslint"]  }}

Notice that I run only the eslint command and not the lint script I highlighted above. If I did the latter, it would run ESLint for the whole project multiple times—for each staged file—without reason, slowing down the pre-commit hook.

Formatting

In addition to linting, a pre-commit hook can also format the code. A popular formatting tool is Prettier. Depending on how you configured Prettier with ESLint, the format command may differ. In the following example, let’s assume that you configured ESLint to run Prettier, and as a result, you can run both tools with the eslint --fix command:

* If you use prettier-eslint, you run prettier-eslint –write; if you use only Prettier, you run prettier –write.

// package.json
{
  "name": "pre-commit-hook",
  // more stuff..
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {    "*.{js,jsx}": ["eslint --fix", "git add"]  }}

As you can see, after we format the code, we run git add to stage any JavaScript files that changed after the formatting. But because we can run ESLint only against our JavaScript files, the rest of our files—CSS, JSON, e.t.c.—remain unformatted. Let’s fix that by creating another file rule that runs prettier to format the remaining files:

// package.json
{
  "name": "pre-commit-hook",
  // more stuff..
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {
    "*.{js,jsx}": ["eslint --fix", "git add"],
    "*.{md,mdx,json,css,scss,yaml,yml}": ["prettier --write", "git add"]  }
}

For more examples on how to use lint-staged, check the documentation.

Optional testing

Finally, you can run a test script— in this case with Jest—in your lint-staged script:

// package.json
{
  "name": "pre-commit-hook",
  // more stuff..
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint --fix",
      "jest --findRelatedTests --bail",      "git add"
    ],
    "*.{md,mdx,json,css,scss,yaml,yml}": ["prettier --write", "git add"]
  }
}

The --findRelatedTests flag instructs Jest to check if the staged files have related tests and run only those. Additionally, with the --bail flag, Jest will stop testing if a single test fails.

Keep in mind, that most people prefer to run only lint and format scripts in their pre-commit hooks and leave test scripts for Continuous Integration. But for simple projects, it’s ok to run test scripts too.

Other things to read

Popular posts

Other notes