Husky with Yarn Monorepo 🐶🐱
Tags: javascript, typescript, yarn, webdev
Published: 2023-05-08 13:00:34

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
```

## 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
```
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 :

```console
$ yarn add -D husky
$ yarn run husky install
```

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


```json
{
  "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"
  }
}
```
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 :

```console
$ yarn run husky add .husky/pre-commit "yarn run pre-commit"
```

Next stage the file :

```console
$ git add .husky/pre-commit
```



## 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 :

```console
$ 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
```

Then create these files :

```console 
$ touch .eslintrc.js .lintstagedrc .prettierrc
```

Copy the following content in **.eslintrc.js** :

```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: [],
}
```

Configure lint-staged through the **.lintstagedrc** file :

```json
{
  "*.ts": ["eslint --fix", "prettier --write"]
}
```

In **.prettierrc** you can add this default configuration :

```json
{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}
```

### Setup Typescript

Run these commands :

```console
$ yarn add -D typescript
$ mkdir src
$ touch src/index.ts tsconfig.json
```

You can use this config in your **tsconfig.json** :

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

### Setup pre-commit 📜

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

```json
  ...
  "scripts": {
    "pre-commit": "lint-staged"
  }
  ...
```

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

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

myFunction()
```

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

```console
$ git add -A
$ git commit -m "My commit message"
```
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 :

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

✖ 1 problem (1 error, 0 warnings)
```

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

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

```typescript
const myFunction = (): string => {
```

Stage it & commit :

```console
$ git add .
$ git commit -m "My commit message"
```

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

```typescript
    return 'This my function !'
``` 

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 :)