DEV Community

Cover image for Enforce Husky Pre-Commit With ESLint & Prettier In Monorepo
Usman Khalil
Usman Khalil

Posted on • Edited on

Enforce Husky Pre-Commit With ESLint & Prettier In Monorepo

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

--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
Enter fullscreen mode Exit fullscreen mode

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:
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,
            },
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode
  • And finally the configuration for prettier. Add a file .prettierrc.js and put the following code:
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,
            },
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode

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:
"lint-staged": {
        "*.{js,ts,tsx, jsx}": [
            "eslint --quiet --fix"
        ],
        "*.{json,md,html,js,jsx,ts,tsx}": [
            "prettier --write"
        ]
    },
Enter fullscreen mode Exit fullscreen mode

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:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

cd frontend
npm run lint-frontend
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (5)

Collapse
 
usmangq12 profile image
usmangq12

It was so helpful. Thanks for sharing.

Collapse
 
metadiego profile image
Diego Olalde

Awesome tutorial, just 2 small fixes:

Under package.json, it should be "lint-frontend": "lint-staged" such that it matches "npm run lint-frontend" on .husky/pre-commit.

In my case, it was also necessary to add:

module.exports = {
env: {
....
node: true
},
...
}

Collapse
 
zirkelc profile image
Chris Cook

Could you share your repo as an example?

Collapse
 
jfbloom22 profile image
Jonathan Flower

I ran into an error ".git can't be found" with this setup. Both lint-staging and husky docs suggest installing them at root in a monorepo. When I did this and kept my "lint-staged": { in the child packages, everything worked well. I expect this is related to the fact that I am linting all my packages with my pre-commit looking like this:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint:all
Enter fullscreen mode Exit fullscreen mode
Collapse
 
leelhn2345 profile image
Nelson Lee

Under the scripts section, rather than "prepare": "cd .. && husky install frontend/.husky", what works for me is "prepare": "cd && husky install .husky".

The former setup another git hook in my frontend folder, while the latter set up the git hook in the root folder.