Need help setting up eslint in a monorepo using Yarn 3 and TypeScript
Asked Answered
K

1

7

I've been scratching my head non stop trying to setup an ESlint configuration in my monorepo. I'm using Yarn 3.2.4 as the package manager. Here is a GitHub repo containing an example project.. Here is the project structure :

/monorepo
├── /configs
│   ├── /eslint
│   │   ├── index.js
│   │   └── package.json
│   ├── /jest
│   │   ├── index.js
│   │   └── package.json
│   ├── /prettier
│   │   ├── index.js
│   │   └── package.json
│   └── /typescript
│       ├── tsconfig.json
│       └── package.json
├── /packages
│   └── /mypackage
│       ├── .eslintrc.js
│       └── package.json
└── package.json

With the following :

  • /monorepo/package.json (Root package.json)
{
  ...,
  "workspaces": [
    "configs/*",
    "packages/*"
  ],
  "scripts": {
    "lint": "yarn workspaces foreach -pt run lint"
  }
}
  • /monorepo/configs/eslint/package.json
{
  ...,
  "name": "@monorepo/eslint-config",
  "main": "index.js",
  "peerDependencies": {
    "eslint": "^8.27.0",
    ...
    * other eslint plugins / configs *
    ...
  }
}
  • /monorepo/configs/eslint/index.js is just my normal eslint config file, extending and using the eslint config packages listed above

  • /monorepo/packages/mypackage/package.json

{
  ...,
  "devDependencies": {
    "@monorepo/eslint-config": "workspace:*",
    "eslint": "^8.27.0",
    ...
    * other eslint plugins / configs *
    ...
  }
}
  • /monorepo/packages/mypackage/.eslintrc.js
module.exports = {
  extends: ['@monorepo/eslint-config']
};

Now with this config, IT WORKS. However what I find really annoying is that I need to specify the eslint configs/plugins dependencies in EVERY package of my mono repo. If I try to place these dependencies in the devDependencies of the eslint-config package, and remove them from the mypackage package (leaving only eslint as a dev dependency) I get the following error message :

Error: Failed to load plugin '@typescript-eslint' declared in '.eslintrc.js » @monorepo/eslint-config': Your application tried to access @typescript-eslint/eslint-plugin, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound.

Required package: @typescript-eslint/eslint-plugin
Required by: /path/to/monorepo/packages/mypackage/

Require stack:
- /path/to/monorepo/packages/mypackage/__placeholder__.js
Referenced from: /path/to/monorepo/.yarn/__virtual__/@monkvision-eslint-config-virtual-abbbf308a5/1/configs/eslint/index.js
    at Function.require$$0.Module._resolveFilename (/path/to/monorepo/.pnp.cjs:16089:13)
    at Function.resolve (node:internal/modules/cjs/helpers:108:19)
    at Object.resolve (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2325:46)
    at ConfigArrayFactory._loadPlugin (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3392:33)
    at ConfigArrayFactory._loadExtendedPluginConfig (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3212:29)
    at ConfigArrayFactory._loadExtends (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3133:29)
    at ConfigArrayFactory._normalizeObjectConfigDataBody (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3074:25)
    at _normalizeObjectConfigDataBody.next (<anonymous>)
    at ConfigArrayFactory._normalizeObjectConfigData (/path/to/monorepo/.yarn/cache/@eslint-eslintrc-npm-1.3.3-9e3a462140-f03e9d6727.zip/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:20)
    at _normalizeObjectConfigData.next (<anonymous>)

So my question is : Is there any way to only declare the ESlint plugin depenencies ONCE (in the config project) and not in every package of my monorepo ?

Sorry for the long post and thanks in advance.

Kuehnel answered 15/11, 2022 at 13:28 Comment(3)
Is there some guide you are following? You seem to be bypassing the benefits of a monorepo with your directory structure and dependency management. The module lookup algorithms search for modules in parent node_modules but not sibling node_modules. Put all your dev dependencies in the root package.json along with your configs.Presbytery
Bare import specifiers use the node resolution algorithm.Presbytery
First, since eslint is a CLI dev dependency, I actually need to list it as a dependency in every package it is used. Second, putting the eslint config dependencies in the root package.json won't work for two reasons : 1) I need them listed as dependencies in my config project in order to be able to publish this package as a standalone package in our registry and 2) the eslint dependency resolution simply does not work at all when I try to do that. Finally, I remind you that I am using yarn 3 with pnp so there is no node_modules in the project :pKuehnel
K
17

Okay so after a few days of reading about this subject, I think I've got a pretty good overview of this subject.

This is actually an issue related to ESlint, and it has nothing to do with yarn workspaces. It is actually a limitation of ESlint that forces shared configs to use peer dependencies. However there are some workarounds / future solutions. For people interested, I will copy / paste the message that I wrote for my team here at work :


Due to the limitations of how ESlint works right now, shared ESlint configurations need to specify their dependencies (such as other configs, plugins etc...) as peer dependencies. This forces developers that want to use our config to manually download each and one of our dependencies. This is obviously very cumbersome and kind of misses the whole point of a shared config. This is even more true when it comes to monorepos like the official [MY COMPANY] repository, in which every single package has the same huge list of ESlint dependencies even though we have, right next to them, a single shared ESlint config. There are ways of bypassing this limitation (@rushstack/eslint-patch etc...) but they come with their own limitations and are considered as "patches" rather than actual features.

However, in August 2022, the ESlint team came up with a new config system called ESlint Flat Config. This new config system allows (among other things) for a shared config to declare its dependencies as dev (or regular) dependencies, and include them in the config file (by importing them as a node package). This is obviously great news for us, since it basically means all of the issues listed in the previous paragraph will be solved. However, we need to give time to ESlint configs and plugins packages to adapt to this new config system, and for now. For instance, the typescript-eslint package created an issue on the 08/11 to update their config system.

This means that for now, we need to stick with the old config and its peer dependencies, and keep an eye on the package updates until the new ESlint config system is mature enough for us to migrate to it.

Some references to read more about this subject :

Kuehnel answered 17/11, 2022 at 16:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.