Create a pre-commit hook

Last update: 12 May, 2019
Table of contents

A pre-commit hook preserves the quality of your code by running lint, format, and/or test scripts before commits. It prevents commits if it finds a problem in the code, or, in other words, if one of the previous scripts fails. A popular tool that is used for setting up pre-commit hooks is husky.

husky, is often used along with another tool the lint-staged. As the name suggests, lint-staged improves the performance of your pre-commit hook by allowing you to run scripts only against the staged files. For example, you don’t have to run the lint script for the entire project but only for the files that changed.

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 fails—it won’t commit the code—if it finds a lint error:

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

Notice that you only run the eslint command in the lint-staged script and not the lint script I highlighted above. If you run the latter, it would run ESLint against the entire project multiple times—for each staged file— and would slow down the pre-commit hook without a reason. Needless to say that I’ve made that mistake in the past.

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 (this is the best way), and as a result, you can run both tools with the eslint --fix command:

package.json
{
  "name": "pre-commit-hook",
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {
    "*.{js,jsx}": ["eslint --fix", "git add"]
  }
}
If you use prettier-eslint, you run prettier-eslint --write; if you use only Prettier, you run prettier --write.

After you format the code, you run git add to stage any JavaScript files that changed after the formatting.

From version 10 onwards, you don’t have to use git add to stage the files because lint-staged adds them automatically in the commit to prevent race conditions. In the next examples, I will omit it.

But because we can run ESLint only against our JavaScript files, the rest of our filesCSS, 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",
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {
    "*.{js,jsx}": ["eslint --fix"],
    "*.{md,mdx,json,css,scss,yaml,yml}": ["prettier --write"]
  }
}

See the lint-staged documentation for more examples.

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",
  "husky": {
    "hooks": {
      "pre-commit": ["lint-staged"]
    }
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint --fix",
      "jest --findRelatedTests --bail"
    ],
    "*.{md,mdx,json,css,scss,yaml,yml}": ["prettier --write"]
  }
}

The --findRelatedTests flag instructs Jest to check if the staged files have tests and run only those tests. 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 the test scripts for Continuous Integration. But for simple projects, it’s ok to run test scripts too.

Other things to read

Popular

Previous/Next