Enforce Husky Pre-Commit With ESLint & Prettier In Monorepo
Tags: git, react
Published: 2021-07-13 04:25:17

Okay, this kept me frustrated for a whole week because I suck at configurations at many levels. But thankfully, I've now closed my 16 chrome tabs and writing this out to make sure you don't have to face the same exact problem.

## Introduction
When different developers work on the same codebase, it becomes necessary to enforce some rules to keep code in check. ESLint and Prettier go hand in hand for this purpose in most of JS projects and integration support is widely available. 

Finally husky is library that allows us trigger actions before committing or pushing. It provides git hooks for this purpose. I'll navigate it to in a minute.

## Problem Statement
The problem that I faced here was that my project was built like a monorepo. It has frontend, backend and library folders in it. In order to use husky git hooks, they are to be placed in the directory where git is placed.

But then again, for husky to work, it needs to utilize package.json file. This issue had me rolling for days. 

## Solution
I'll navigate step by step from installing husky to committing the code. This might takes quite a few commands, so please bear with me.

#### Installing husky
In the root folder of the repo where git resides, run following commands:
```
npx husky install
npx husky add .husky/pre-commit "npm test"
```
This will create a `.husky` folder in the root directory with `pre-commit` file in it. This file would have a single command npm test in it. 
```
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test
```
Let's leave it for now and move to next step.

#### Installing Linters
Go to your frontend project and install eslint, husky and prettier with the following commands:
```
npm install husky lint-staged eslint-plugin-prettier eslint-config-prettier --save-dev
npm install --save-dev --save-exact prettier
```

`--save-dev` keeps these libraries in `devDependencies` because they won't be used in production and are here for development only.

### Configuring Linters:
We'll be creating few files to let our linters know how they would be working across the project.
- Create `.estlintignore` and `.prettierignore` files and place the following code
```
build
node_modules
.github
```
This will inform our linters not to look into files in the above mentioned directories
- Now we'll be adding few configurations for estlint. Create a file `.eslintrc.js` with this code:
```js
module.exports = {
    env: {
        browser: true,
        es6: true,
    },
    extends: [
        'eslint:recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        'plugin:prettier/recommended',
        'plugin:jsx-a11y/strict',
    ],
    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true,
        },
        ecmaVersion: 2018,
        sourceType: 'module',
    },
    plugins: ['react', 'jsx-a11y', '@typescript-eslint'],
    rules: {
        'react-hooks/exhaustive-deps': 'error',
        'no-var': 'error',
        'brace-style': 'error',
        'prefer-template': 'error',
        radix: 'error',
        'space-before-blocks': 'error',
        'import/prefer-default-export': 'off',
    },
    overrides: [
        {
            files: [
                '**/*.test.js',
                '**/*.test.jsx',
                '**/*.test.tsx',
                '**/*.spec.js',
                '**/*.spec.jsx',
                '**/*.spec.tsx',
            ],
            env: {
                jest: true,
            },
        },
    ],
};
```

- And finally the configuration for prettier. Add a file `.prettierrc.js` and put the following code:
```js
module.exports = {
    printWidth: 100,
    tabWidth: 2,
    singleQuote: true,
    semi: true,
    trailingComma: 'all',
    arrowParens: "always",
    overrides: [
        {
            files: '*.{js,jsx,tsx,ts,scss,json,html}',
            options: {
                tabWidth: 4,
            },
        },
    ],
};
```

#### Setting Up Package.json
We're almost there and now we'll have to add few scripts to package.json. I'll guide you about their purpose along the way.
- Add the following line in the scripts section:
`"prepare": "cd .. && husky install frontend/.husky"`
`npm prepare` command runs before we commit our code. What essentially we're doing here is that we are moving out of frontend directory and installing husky in the front-end. 
- Now we need to add our linting configuration governed by `lint-staged`. Place the following code after scripts section:
```json
"lint-staged": {
        "*.{js,ts,tsx, jsx}": [
            "eslint --quiet --fix"
        ],
        "*.{json,md,html,js,jsx,ts,tsx}": [
            "prettier --write"
        ]
    },
```
We've written the extensions of the files eslint and prettier would be amending.

- Finally, we'll be adding a script that would invoke linting. Add this line in your scripts:
`"lint-front": "lint-staged"`
Running `npm run lint-front` would trigger linting our application. 

- Let's just inform our husky to run `npm run lint-front` before commit. Go to the husky folder in the project root and replace pre-commit file with this code:
```sh
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

cd frontend
npm run lint-frontend
```

If everything followed correctly, making a commit would trigger linting. That's the end of it. Hope this helps someone. If you are still having issue, do mention the comments. I'd be more than happy to help.