Pre-commit Hooks
Husky isn't just a dog
Photo by Tk on Unsplash
I had no idea what pre-commit hooks or Husky were until recently. Then I stumbled upon this video by a Microsoft developer (sadly couldn't find him online anymore). He had this amazing Windows 11 emulator website portfolio. Sure, there are many such setups online, but the attention to detail? My goodness, it was inspiring!
Long story short, I went through his setup guide that covered everything from linters to prettier to hooks. When I saw pre-commit hooks. After some searching, I was amazed to discover how easy it was to set up.
Why Even Bother with Pre-commit Hooks?
Here's the situation at my workplace: I work with people who code but with... let's say, minimal enthusiasm. Their coding environment? Basic. Like, really basic.
If you're using VSCode, you're probably smart enough to install basic linters and prettiers to make everyone's life easier. But that wasn't happening.
In my Neovim setup, I've done lots of minimal configurations over time to improve the overall linting and formatting with LSPs. When I write code and save, my LSP formats it for me before saving. Then I commit and push. Simple, right?
But here's where it gets messy. People not using any formatting or prettiers change hundreds of lines of code with no formatting at all. Then I pull their changes and modify a single word... save... and boom! Git diff shows 502 lines changed!
Sure, I know what I changed, but what if I changed 200 lines of code? How do I know that those other 302 lines were just formatting should, I just go through all 500 lines and check because my colleague didn't use any formatters?
The Pleasant Surprise: Husky + lint-staged
When I first encountered pre-commit hooks, I thought, "This must be complicated to set up." But it turned out to be the easiest thing ever. Here's how it works:
- First, set up Husky in your project:
npm install husky --save-dev
npx husky install
npm pkg set scripts.prepare="husky install"
- Install lint-staged and ESLint plugin for browser compatibility:
npm install --save-dev lint-staged
npm install eslint-plugin-compat@latest --save-dev
- Create a pre-commit hook:
npx husky add .husky/pre-commit "npx lint-staged"
This creates a simple pre-commit hook that only runs lint-staged. No complexity, just:
npx lint-staged
- Create
.lintstagedrc.json
for lint-staged configuration:
{
"*.tsx": ["prettier --write", "eslint"],
"*.ts": ["prettier --write", "eslint"],
"*.html": ["eslint", "prettier --write"],
"*.scss": "prettier --write",
"*.css": "prettier --write"
}
- Set up your
.eslintrc.json
:
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-prettier eslint-config-prettier
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest/globals": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"react-hooks",
"@typescript-eslint",
"prettier",
"jest"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/no-explicit-any": "warn",
"prettier/prettier": ["error", {
"endOfLine": "auto"
}]
}
}
Now, whenever someone clones our repo, they run npm run prepare
which sets up Husky. Then, every time they try to commit:
- Husky intercepts the commit
- Runs lint-staged
- lint-staged applies Prettier and ESLint to the staged files based on their extensions
- The commit only goes through if all checks pass
The beauty of this setup is that:
- It only checks files that are staged for commit
- Different file types get different treatments (like how
.scss
only gets Prettier while.tsx
gets both Prettier and ESLint) - ESLint is configured to check browser compatibility and React-specific issues
- The pre-commit hook is simple and focused: just
npx lint-staged
And if someone doesn't run npm run prepare
and just pushes unformatted code... well, these people should really rethink their life choices 😅
Conclusion
You know what's funny? All these amazing tools exist, but sometimes the fear of complexity stops me from trying them. I was afraid pre-commit hooks would be difficult to implement, but here I am.
Gracias.