Husky with Yarn Monorepo 🐢🐱

0x2a

Lucas Martin

Posted on May 8, 2023

Husky with Yarn Monorepo 🐢🐱

Hi everyone ! This article will show you how you can setup a Yarn monorepo to work with Husky.

Source code πŸ“¦ : https://github.com/0x2A-git/husky-yarn-monorepo-tutorial/tree/main

Goal ⚽

We are going to take a common example where we'd like to automatically lint all of our staged changes (regardless of their workspace) when committing using ESLint & Prettier.

Project structure πŸ—οΈ

.
|-- apps/
|    app/
|     ...
|     package.json
|-- packages/
|    package/
|     ...
|     package.json
`-- package.json
Enter fullscreen mode Exit fullscreen mode

Monorepo Setup 🚧

First, upgrade yarn to the latest stable version and add workspace-tools plugin :

$ yarn set version stable
$ yarn
$ yarn plugin import workspace-tools
Enter fullscreen mode Exit fullscreen mode

Note : If you don't want to use PnP and stick with node_modules run yarn config set nodeLinker node-modules

Once it is done, install Husky as a dependency of your whole monorepo :

$ yarn add -D husky
$ yarn run husky install
Enter fullscreen mode Exit fullscreen mode

Then open your monorepo's package.json and add the following scripts :

{
  "name": "husky-yarn-monorepo",
  "packageManager": "yarn@3.5.1",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "devDependencies": {
    "husky": "^8.0.3"
  },
  "scripts": {
    "postinstall": "husky install",
    "pre-commit": "yarn workspaces foreach run pre-commit"
  }
}
Enter fullscreen mode Exit fullscreen mode

The pre-commit script will run a script called pre-commit script in every app & package package.json.

Now we are going to register the pre-commit script to Husky :

$ yarn run husky add .husky/pre-commit "yarn run pre-commit"
Enter fullscreen mode Exit fullscreen mode

Next stage the file :

$ git add .husky/pre-commit
Enter fullscreen mode Exit fullscreen mode

App / Package Setup 🚧

For the tutorial convenience the app and the package will have the same architecture. This part assumes that you've already setup your workspaces (yarn init, yarn, ...). Let's cd in one of the apps or the packages and follow these steps :

Setup Linting :

First install these dependencies :

$ yarn add -D eslint @typescript-eslint/eslint-plugin eslint-config-prettier eslint-config-standard-with-typescript eslint-plugin-import eslint-plugin-n eslint-plugin-prettier eslint-plugin-promise lint-staged prettier
Enter fullscreen mode Exit fullscreen mode

Then create these files :

$ touch .eslintrc.js .lintstagedrc .prettierrc
Enter fullscreen mode Exit fullscreen mode

Copy the following content in .eslintrc.js :

const path = require('path')

module.exports = {
    env: {
        es2021: true,
    },
    extends: ['standard-with-typescript', 'prettier'],
    parserOptions: {
        project: path.join(__dirname, '/tsconfig.json'),
        ecmaVersion: 'latest',
        sourceType: 'module',
    },
    overrides: [],
}
Enter fullscreen mode Exit fullscreen mode

Configure lint-staged through the .lintstagedrc file :

{
  "*.ts": ["eslint --fix", "prettier --write"]
}
Enter fullscreen mode Exit fullscreen mode

In .prettierrc you can add this default configuration :

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}
Enter fullscreen mode Exit fullscreen mode

Setup Typescript

Run these commands :

$ yarn add -D typescript
$ mkdir src
$ touch src/index.ts tsconfig.json
Enter fullscreen mode Exit fullscreen mode

You can use this config in your tsconfig.json :

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": "./src",
    "outDir": "./dist",
    "declaration": true,
  },
  "include": ["./**/**.ts"]
}
Enter fullscreen mode Exit fullscreen mode

Setup pre-commit πŸ“œ

Add the following script in your app's or package's package.json :

  ...
  "scripts": {
    "pre-commit": "lint-staged"
  }
  ...
Enter fullscreen mode Exit fullscreen mode

You can copy the following code in src/index.ts :

const myFunction = () => {
  return "This my function !"
}

myFunction()
Enter fullscreen mode Exit fullscreen mode

Now let's try to commit our files by running :

$ git add -A
$ git commit -m "My commit message"
Enter fullscreen mode Exit fullscreen mode

Note : If you get Usage Error: Couldn't find a script named "pre-commit". don't forget to implement pre-commit script in every app / package package.json.

After running the command you should get this error :

  1:20  error  Missing return type on function  @typescript-eslint/explicit-function-return-type

βœ– 1 problem (1 error, 0 warnings)
Enter fullscreen mode Exit fullscreen mode

Nice ! That's what we wanted, it means that ESLint successfully detect errors.

Let's edit our source code to fix this error :

const myFunction = (): string => {
Enter fullscreen mode Exit fullscreen mode

Stage it & commit :

$ git add .
$ git commit -m "My commit message"
Enter fullscreen mode Exit fullscreen mode

Great, the commit has been made ! If you look at :

    return 'This my function !'
Enter fullscreen mode Exit fullscreen mode

You should notice that both quotation marks have been replaced by apostrophes thanks to Prettier.

Conclusion πŸ‘

Well done ! You've successfully setup a linting mechanism on your workspaces thanks to Husky πŸ‘Œ

If you have any question about this article, feel free to ask it in the comments.

Have a nice day :)

πŸ’– πŸ’ͺ πŸ™… 🚩
0x2a
Lucas Martin

Posted on May 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related