aicevas
Navigate back to the homepage
About

How ESLint Resolves Plugins And Shareable Configs

Tomas Zaicevas
Tomas Zaicevas
February 22nd, 2022 · 3 min read
How ESLint Resolves Plugins And Shareable Configs

TLDR: plugins are searched for relative to the end user’s project, while shareable configs are searched for relative to the config file where they appear.

If you’ve used eslint-config-airbnb (one of the most popular ESLint shareable configs), you might have wondered why do you have to install so many peer dependencies: eslint-plugin-import, eslint-plugin-jsx-a11y, eslint-plugin-react, eslint-plugin-react-hooks. Can’t eslint-config-airbnb bring those dependencies?

Alternatively, if you’ve ever built an ESLint plugin or a shareable config, you’ve most likely faced the conundrum of how ESLint resolves plugins and shareable configs.

This is what I will answer in this blog post. For most folks, it is not crucial to understand. However, it becomes extremely useful when you are building your own ESLint plugin or a shareable config. Aaand it’s also a pie for curious minds 😏


Plugins vs shareable configs

First of all, ESLint has plugins and it has shareable configs. These are two different things:

  • Shareable config — holds a reusable config (think of it as a ruleset). Example: eslint-config-airbnb
  • Plugin — can do everything that a shareable config does + might include custom rules. Example: eslint-plugin-jest

For more practical examples, https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/ is a clear and lightweight read.

Note: I will use config and shareable config interchangeably.

Plugins and configs are resolved differently

Surprisingly, when ESLint searches for plugins and configs, it looks into different paths. It is described in ESLint docs:

Relative paths and shareable config names in an extends property are resolved from the location of the config file where they appear. https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files

Plugins in the base configuration (loaded by extends setting) are relative to the derived config file. https://eslint.org/docs/user-guide/configuring/plugins#configuring-plugins

Derived config file means consumer project config file, such as your-project/eslintrc.js

It might still sound cryptic, though. Let me simplify that for you:

  • Config — if you extend eslint-config-airbnb, and eslint-config-airbnb extends eslint-config-prettier, ESLint searches for your-project/node_modules/eslint-config-airbnb/node_modules/eslint-config-prettier
  • Plugin — if you extend eslint-config-airbnb and eslint-config-airbnb extends eslint-plugin-react, ESLint searches for your-project/node_modules/eslint-plugin-react

In other words, ESLint wants all shareable config consumers to install all of the plugins.

This is why eslint-config-airbnb includes plugins as peer dependencies.

It is also stated in ESLint docs:

If your shareable config depends on a plugin, you should also specify it as a peerDependency (plugins will be loaded relative to the end user’s project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as dependencies. https://eslint.org/docs/developer-guide/shareable-configs#publishing-a-shareable-config

Self-contained ESLint plugin/config

If you are wondering why ESLint cannot tweak this behavior and allow ESLint plugins/configs to be self-contained (bring other plugins as dependencies), then you are not alone.

There is a GitHub issue dating back to 2015: Support having plugins as dependencies in shareable config. There’s quite some drama as well 😅 And there is even an ESLint patch that helps achieve this. Although, personally, I would be hesitant to utilize it because it requires all consumer projects to explicitly use it in config files.

ESLint team is already working on implementing a Flat Config, meaning ESLint will leave it to the users to call require() and resolve plugins/configs.

However, self-contained plugin/config is already possible 😈

This is where the nuances of different package managers get into the picture.

You see, package managers are the masters of node_modules. If a package manager is able to put transitive dependencies (e.g. dependencies of eslint-config-airbnb) into your-project/node_modules, they can be resolved by ESLint.

Different package managers have different ways of managing node_modules:

  1. npm v3 and later — flat node_modules

  2. yarn v1— flat node_modules

  3. yarn v2 — Plug’n’Play ❌

  4. pnpmhoists ESLint plugins/configs

As you can see, yarn v2 is the odd one. npm and pnpm already put ESLint transitive dependencies directly into your-project/node_modules.

In other words, if you install eslint-config-airbnb and it brings eslint-plugin-import as a dependency, it will be located in your-project/node_modules/eslint-plugin-import, rather than in your-project/node_modules/eslint-config-airbnb/node_modules/eslint-plugin-import or somewhere else.

Thus, if an ESLint plugin/config can afford NOT to support yarn, putting other ESLint plugins into dependencies facilitates achieving a self-contained plugin/config. Meaning it is not required for consumer projects to install plugins as peer dependencies.

However, just to reiterate, it wouldn’t work with yarn v2, so I am not surprised that eslint-config-airbnb does not include plugins as dependencies.

Summary

I hope you found this blog post useful! Let me sum up:

  • ESLint plugins and shareable configs are two different things

  • ESLint resolves plugins and configs differently

  • Plugins are searched for relative to the end user’s project (your-project/node_modules/eslint-plugin-foo)

  • Configs are searched for relative to the config file where they appear

  • Since plugins are searched for relative to the end user’s project, it is recommended to put them as peerDependencies in ESLint config/plugin, meaning consumer projects have to install them

  • Plugins and configs are able to bring other plugins as dependencies, as opposed to peerDependencies. This is compatible with npm ✅ and pnpm ✅, but not yarn v2 ❌

What NOT To Assert in React Component Tests

What NOT To Assert in React Component Tests

Avoid these assertions in Jest, React Testing Library tests.

February 8th, 2022 · 6 min read
How Performing Classical Music Helped Me Code

How Performing Classical Music Helped Me Code

From winning tens of international accordion competitions to becoming a Frontend Developer.

December 2nd, 2021 · 4 min read
Copyright © 2023 Tomas Zaicevas
Link to $https://twitter.com/tozaicevasLink to $https://github.com/zaicevasLink to $https://linkedin.com/in/tomas-zaicevasLink to $https://zaicevas.medium.com/