Last week I talked about ESLint and its usefulness for keeping projects consistent amongst multiple contributors. If you haven't read that post I recommend doing so before diving into this one.
Today, we're going to focus on running ESLint automatically to ensure that the main branch of your project always follows your specific ruleset.
Lint-staged
The first tool to talk about is lint-staged. Lint-staged is configured in your package.json file.
{
"lint-staged": {
"*.js": "eslint --fix"
}
}
As seen in the above example, you can use a glob pattern to tell lint-staged which files to run against. Additionally, you can give lint-staged a command to execute against those files. In many cases, you'll want more than one command, which lint-staged supports. In this case, you'll run ESLint and prettier.
{
"lint-staged": {
"*.js": ["eslint", "prettier --write"]
}
}
So how does lint-staged work? It's specifically designed to work on "staged" files, thus the name. This means files you've changed or created but haven't yet committed to your project. Working on staged files limits the number of files you need to lint at any given time and makes the workflow faster. The commands you configure will run "pre-commit". As you're attempting to commit files to your project you'll see ESLint run in your terminal. Once it's done you may have successfully committed or find yourself with linting errors you need to fix before you're able to commit the code.
However, what you may not realize, is that lint-staged is not the only tool working under the hood. Lint-staged is designed to work with another tool called husky.
Husky
You may have come across husky before without noticing. For many years it was configured via a few lines of code in your package.json file. Something like this.
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
However, the latest version of husky, v6, has changed this approach. Now, husky uses distinct bash files with filenames that match the workflow step they correspond to, e.g. "pre-commit". Luckily you don't have to set this up yourself and husky has a nice CLI command to do it for you.
npx husky-init && npm install
npx husky add .husky/pre-commit "npm test"
The first line of the command is a one-time initialization script that ensures all your coworkers will have husky installed on their machine before they try to commit files.
The second line creates the pre-commit
file inside the .husky
directory. If you look at the file you'll notice it's running a husky.sh
script prior to whatever commands you initialized it with. This can technically be removed, but I'd recommend keeping it. The script allows for a few things, including the use of a --no-verify
flag that bypasses the checks.
Once you've initialized the directory and associated file you can add whatever commands you want to it. In my case, I replaced npm test
with npm lint-staged
.
Pre-push
The pre-commit
workflow is more or less the husky happy path. But what if your project doesn't want to lint on commit and would prefer to lint when a developer attempts to push their changes to a branch?
While it's tempting to create a .husky/pre-push
file and run lint-staged, it won't work. The pre-push
husky workflow is correct, but running lint-staged at that point will turn up 0 matching files. This makes sense, though it certainly messed me up for a bit, because committed files are no longer staged. Instead, you have a couple of options.
- Run ESLint against all the files:
eslint '*.js'
- Diff against
main
:eslint --no-error-on-unmatched-pattern $(git diff main... --name-only --- '*.js')
Note that this is one example of a diff command and there are numerous considerations depending on your project.
Next steps and CI
Running ESLint, or prettier, or even tests as part of your git workflow is important because it helps you fail fast. However, it's not a replacement for CI checks. Typically, you'll want to run these commands in both environments to ensure nothing slips through.
But altogether these tools help ensure a cleaner, more consistent production codebase. Long term, that's a big win for any project.
Top comments (4)
Use npx lint-staged instead of npm run lint-staged. npm run can only execute the scripts specified in the scripts section of your package.json
or
In .husky/pre-commit change npm run lint-staged to npm run pre-commit
Add
"pre-commit": "lint-staged"
to package.json in "scripts"Add lint-staged config like you've always done:
Github Issue #949 How to use husky v6 with lint-staged?
Thx for this! This is really what I wanted. Helped A LOT.
Can I translate in Korean this article? If you don't mind, I wanna share this awesome information in Korean. Surely, There will be a link directing to this original one.
Of course, I’d be honored
No need to pollute the
package.json
file. One should create a new file.lintstagedrc
or any of the flavors advised by lint-staged