Photo by Vishal Jadhav from Unsplash.
It is important to have a uniform coding style in a project, especially for a JavaScript/TypeScript project. This is because JavaScript is a very flexible language, while TypeScript is less flexible in syntax but is still quite flexible in formatting. Imagine some part of the code uses 2 spaces for indentation and other part uses 4. Well, you may think linting like this is just cosmetic (which I don’t agree with), linters can check the syntax as well and help you improve your code quality with common industry standards. It is cumbersome to check the code quality and coding styles manually. Luckily with Git Hooks, it can be done automatically and the author will not be able to create commits if linting cannot be passed. For JavaScript/TypeScript projects, there are some well-established packages that can be used for linting and managing Git hooks. In this post, we will introduce how to create a Git pre-commit hook to check JavaScript/TypeScript code using ESLint, Prettier, lint-staged, and Husky.
Install and configure ESLint
ESLint is more complex to set up than other packages used in this post. It’s recommended to check this post first for ESLint, especially if you want to use it to check TypeScript code. That post can help you solve all kinds of issues when you set up ESLint for your project. If you just want to have a quick setup for ESLint, you can run this command to install and configure ESLint with a wizard:
$ **npm init** [**@eslint/config**](http://twitter.com/eslint/config)
Select the options that best suit your project and the package.json
file will be updated accordingly. A [_.eslintrc.js_](https://gist.github.com/lynnkwong/a3300c1b8e0a38348a619bfafcb6812d#file-1170bbdff32b-eslintrc-1-js)
file will be created automatically based on the choices you made. You would need to make some changes to meet your requirements. This is the [_.eslintrc.js_](https://gist.github.com/lynnkwong/a3300c1b8e0a38348a619bfafcb6812d#file-1170bbdff32b-eslintrc-1-js)
file that will be used in this post. More details for it can be found in the ESLint post.
Install and configure Prettier
If your project only has JavaScript and TypeScript code, ESLint is normally enough because it can perform both syntax and format checking. However, if your project also has HTML and CSS files, you would need to install Prettier as well, a specialized tool for code formatting. Besides, since Prettier is available in most IDEs (like VS Code), it’s more convenient to use Prettier than ESLint for automatic code formatting.
Run the following command to install Prettier:
Note that besides the Prettier package, two packages for ESLint are also installed so it can work nicely with ESLint as the two have some overlap for code formatting.
The default settings of Prettier are normally enough in most cases. However, if your project needs some special styling, you can add them to a file named .prettierrc.json
file in the project root folder. You can add the options on the command line directly if there are not too many of them. Check this link for all the format options for Prettier.
Install and configure lint-staged
By default, the linters like ESLint and Prettier will check all the files in the project. This, however, is very inefficient. When creating a commit, we only want the linters to check the files that are changed or staged. A file is staged means you have run git add
to add it to the staging area. This is where the lint-staged package shines, which lets us only lint the files that are staged.
To install lint-staged, run this command:
$ **npm install --save-dev lint-staged**
To configure lint-staged, you can either create a new object called “lint-staged
” in package.json
, or create a new file named [.lintstagedrc.json](https://github.com/okonet/lint-staged#configuration)
in the project folder. The latter is better if you have complex settings. However, we will use the former as there are only a handful of settings:
Here we define three tasks for lint-staged. By default, lint-staged will run the tasks concurrently. Therefore, we should pay extra attention if the same file is modified by different tasks. The result can be unpredictable due to race conditions. We should make sure the settings of different linters have no conflict. If necessary, we can disable concurrency by with --concurrent false
on the command line for lint-staged
:
# Run the tasks concurrently:
$ **npx lint-staged**# Run the tasks sequentially:
$ **npx lint-staged --concurrent false**
Note that, when a task fails, all the other tasks will be skipped or killed.
Install and configure Husky
Above we ran lint-staged manually with npx
. If we want to enforce linting before a commit is created, we should run lint-staged in the pre-commit Git hook. The pre-commit hook is actually just a runnable script that is run before a commit is created. We can have any code in the pre-commit hook and the linting code is a perfect use case.
There are three ways to add the linting code for lint-staged in a pre-commit hook:
- Create a pre-commit hook manually and add the above command to run lint-staged in it. This is a bit cumbersome because you need to copy the pre-commit hook to the
.git/hooks/
folder manually. - Use the pre-commit installer. This is convenient if we only have pre-commit hooks, and no other types of Git hook like commit-msg or pre-push.
- Use Husky! Husky supports all Git hooks. This is currently the most popular way to manage Git hooks in a JavaScript/TypeScript project and will be used in this post. To install husky and initialize a project with husky, run this command:
$ **npx husky-init && npm install**
A new folder named .husky
will be created in your project folder which includes a special folder named with an underscore “_
” and a template pre-commit hook. Besides, there is a new script named prepare
added in package.json
, which runs the husky install
command. The [prepare](https://docs.npmjs.com/cli/v8/using-npm/scripts#life-cycle-scripts)
script will create the .husky
folder before the package is packed.
Let’s add the command to run lint-staged in the pre-commit hook in the .husky
folder:
Thispre-commit hook will be called before you create a commit in the current project. We need to add it to the project folder as well so it will persist in the project when it’s pushed to the remote repository. Don’t worry, it won’t be overwritten by the husky install
command.
$ **git add package.json**
$ **git add package-lock.json**
$ **git add .husky/pre-commit**
$ **git commit**
Now when you try to create a commit for a file matching the patterns defined by lint-staged as shown above, the pre-commit hook will be called automatically. And only the staged files will be checked: Cheers! We have now successfully set up a Git pre-commit hook in our project, which enforce a coding and formatting standard for this project. Now if anyone tries to add a file that violates the rules defined by the linters, he/she would not be able to commit. You may hate it in the beginning but you will love it over time, especially when you have some new members in your team who don’t know the coding style of your group yet. It will make your codebase less error-prone, easier to maintain, and more enjoyable to read. All the code introduced in the post can be found in this repo.