ESLint in a monorepo
Installing ESLint
Keeping a single version of ESLint is recommended for simplicity. Because of this, we suggest installing ESLint as devDependency
at the root of your monorepo, or utilizing a tool like syncpack (opens in a new tab) to maintain the same version across workspaces.
Sharing config
Sharing an ESLint config across workspaces can be a boon to productivity by making all your workspaces more consistent.
Let's imagine a monorepo like this:
apps
├─ docs
│ ├─ package.json
│ └─ .eslintrc.js
└─ web
├─ package.json
└─ .eslintrc.js
packages
└─ eslint-config-custom
├─ next.js
├─ library.js
└─ package.json
We've got a package called eslint-config-custom
, and two applications, each with their own .eslintrc.js
.
Our eslint-config-custom
package
Our eslint-config-custom
file contains two files, next.js
, and library.js
. These are two different ESLint configs, which we can use in different workspaces, depending on our needs.
Let's investigate the next.js
lint configuration:
const { resolve } = require("node:path");
const project = resolve(process.cwd(), "tsconfig.json");
/*
* This is a custom ESLint configuration for use with
* Next.js apps.
*
* This config extends the Vercel Engineering Style Guide.
* For more information, see https://github.com/vercel/style-guide
*
*/
module.exports = {
extends: [
"@vercel/style-guide/eslint/node",
"@vercel/style-guide/eslint/typescript",
"@vercel/style-guide/eslint/browser",
"@vercel/style-guide/eslint/react",
"@vercel/style-guide/eslint/next",
// turborepo custom eslint configuration configures the following rules:
// - https://github.com/vercel/turbo/blob/main/packages/eslint-plugin-turbo/docs/rules/no-undeclared-env-vars.md
"eslint-config-turbo",
].map(require.resolve),
parserOptions: {
project,
},
globals: {
React: true,
JSX: true,
},
settings: {
"import/resolver": {
typescript: {
project,
},
},
},
ignorePatterns: ["node_modules/", "dist/"],
// add rules configurations here
rules: {
"import/no-default-export": "off",
},
};
It's a typical ESLint config that extends the Vercel styleguide (opens in a new tab), nothing fancy.
The package.json
looks like this:
{
"name": "eslint-config-custom",
"license": "MIT",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@vercel/style-guide": "^4.0.2",
"eslint-config-turbo": "^1.10.12"
}
}
Note that the ESLint dependencies are all listed here. This is useful - it means we don't need to re-specify the dependencies inside the apps which import eslint-config-custom
.
How to use the eslint-config-custom
package
In our web
app, we first need to add eslint-config-custom
as a dependency.
{
"dependencies": {
"eslint-config-custom": "*"
}
}
We can then import the config like this:
module.exports = {
root: true,
extends: ["custom/next"],
};
By adding custom/next
to our extends
array, we're telling ESLint to look for a package called eslint-config-custom
, and reference the file next.js
.
You can avoid specifying the file by setting the entrypoint for your package. For example, setting main: 'next.js'
in the package.json
, could be loaded as simply extends: ["custom"]
in your .eslintrc.js
.
Summary
This setup ships by default when you create a new monorepo with npx create-turbo@latest
. You can also look at our basic example (opens in a new tab) to see a working version.
Setting up a lint
task
We recommend following the setup in the basics
section, with one alteration.
Each package.json
script should look like this:
{
"scripts": {
"lint": "eslint ."
}
}