How to make ESLint configs shareable
Often times I want to use the same base set of plugins and rules across projects. To do this, I should be able to:
- Create a new NPM package, which defines this configuration
- Add that package as a development dependency of my project
"extends: my-shared-config"in my project's
Unfortunately, it's not this straightforward out of the box, due to how ESLint plugins are resolved.
Lets take a look at an example project:
Sharing ESLint configurations does not work in the same way that NPM packages do
It seems like
sharedEslint.config.js should be able to resolve
eslint-plugin-import on behalf of the top-level
eslint.config.js file in the project.
This is how things work when using other NPM packages. For example,
@apollo/client is able to import and use the
However we get an error message like this when trying to run the linter:
The problem is that ESLint plugins are resolved relative to the configuration that is extending the shared configuration, rather than from the shared configuration.
This means is that every plugin must be explicitly installed as a dev dependency of my project, otherwise we run the risk of things breaking if the package manager (
pnpm, etc.) doesn't hoist
the plugins to the top-level of the project's
node_modules directory. It also means that any engineers that use the
sharedLinterConfig package, need to know about the implementation details.
Fixing the problem
There is a workaround, which allows ESLint plugins to be resolved like other modules: @rushstack/eslint-patch.
This fix will change the default behavior of ESLint. This could break packages that operate on the assumption that all ESLint plugins are explicitly installed by a project.
To use the patch, add the following snippet to your shared ESLint configuration file:
Now, a package.json that had to be set up like this:
Can be simplified to:
sharedLinterConfig will manage the versions of all the various plugins.
The engineers working on
new-web-project can just use the package and not worry too much about its
implementation details. That should be the responsibility of the owners of
You may notice that there are still some ESLint configuration-related dependencies listed here. Since these are not plugins, they will not be handled by
I'll walk through how to handle these particular packages in the next section.
Bonus Fun: Handling resolution within ESLint plugins
As with any workaround, there are bound to be some rough edges. I ran into a few with the
eslint-plugin-import package allows you to configure resolvers and parsers.
This can be very useful if you are using import aliases with a tool like: Webpack, TypeScript, Babel, etc. and want to allow them to be analyzed properly by the linter.
To preserve the abstraction that our
In the case of
eslint-plugin-import, here's how I was able to configure things:
After doing this, the
package.json of our example application looks like this:
@rushstack/eslint-patch workaround makes it possible to create shareable ESLint configurations, that provide the same level of abstraction as an NPM package.
This approach is not without its drawbacks, as mentioned in the previous section, and you should consider the tradeoffs carefully.
If you are working in a large engineering organization, the benefits of project standardization and consistency are significant. If you've already got buy-in from teams on a standard set of ESLint rules, I'd highly recommend trying
@rushstack/eslint-patch and see how it works for your teams.