This is an alpha, sneak peek of Monorepo Maestros. For this iteration, I'm getting all of my thoughts down. In the future, we'll have better information architecture, graphics, and other awesomeness. Your feedback is welcome!
ESLint
ESLint is the most common linter used in the JavaScript world today. Setting it up in a monorepo can be difficult and piecing together the various configurations can be frustrating if you don't know what you need to do.
But we're maestros! We can do this. Let's take a look at how to happily set up ESLint for JavaScript monorepo success.
To create some presets for our workspaces, we'll set up a workspace with our configurations in tooling/eslint-config.
tooling
eslint-config
.eslintrc.js
next.js
node.js
svelte.js
package.json
Our package.json will be relatively simple, installing eslint and exporting the files where we'll keep our presets.
tooling/eslint-config/package.json
{
"name": "@repo/lint",
"version": "0.0.0",
"files": ["node.js", "next.js", "svelte.js"],
"scripts": {
"lint": "eslint ."
},
"dependencies": {
"@next/eslint-plugin-next": "latest", // We'll need this for the Next.js config in a moment.
"eslint": "^8.40.0"
}
}
tooling/eslint-config/package.json
{
"name": "@repo/lint",
"version": "0.0.0",
"files": ["node.js", "next.js", "svelte.js"],
"scripts": {
"lint": "eslint ."
},
"dependencies": {
"@next/eslint-plugin-next": "latest", // We'll need this for the Next.js config in a moment.
"eslint": "^8.40.0"
}
}
tooling/eslint-config/package.json
{
"name": "@repo/lint",
"version": "0.0.0",
"files": ["node.js", "next.js", "svelte.js"],
"scripts": {
"lint": "eslint ."
},
"dependencies": {
"@next/eslint-plugin-next": "latest", // We'll need this for the Next.js config in a moment.
"eslint": "^8.40.0"
}
}
tooling/eslint-config/package.json
{
"name": "@repo/lint",
"version": "0.0.0",
"files": ["node.js", "next.js", "svelte.js"],
"scripts": {
"lint": "eslint ."
},
"dependencies": {
"@next/eslint-plugin-next": "latest", // We'll need this for the Next.js config in a moment.
"eslint": "^8.40.0"
}
}
The lint script in package.json is for linting the eslint-config
workspace itself. It is not the script that runs in your other workspaces.
It's typical that not all of our workspaces will use the exact same linting configuration. As an example, default exports tend to be inadvisable for JavaScript modules but some frameworks require default exports to work properly (e.g. A Next.js page.js file needs a default export). We can account for this by creating multiple base configurations.
To build off of our presets for any specific needs in a particular workspace, you can leverage the overrides property of ESLint. It may look something like this:
Once we've created our linting scripts in any workspaces that we want to lint, it's time to build up our Turborepo pipeline.
turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"lint": {
"outputs": ["node_modules/.cache/.eslintcache"],
"dependsOn": ["^topo"]
}
}
}
turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"lint": {
"outputs": ["node_modules/.cache/.eslintcache"],
"dependsOn": ["^topo"]
}
}
}
turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"lint": {
"outputs": ["node_modules/.cache/.eslintcache"],
"dependsOn": ["^topo"]
}
}
}
turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"lint": {
"outputs": ["node_modules/.cache/.eslintcache"],
"dependsOn": ["^topo"]
}
}
}
Recursively depending on topo: This is a little trick from the Turborepo documentation. In short, this dependsOn pattern flattens your task graph so that everything runs in parallel while still respecting changes in workspace dependencies. (If that sounds confusing, don't worry about it for now; just trust that it works. We'll be writing up a doc for this but, for the time being, let it be magic.) ✨
Using ESLint caching: We're about to create an .eslintcache file in our workspaces in the next step. In the event that our task misses cache, we will still have the .eslintcache to use to speed up our task. Caching this file as a Turborepo output ensures that we have the .eslintcache shared across our machines as often as possible so we can use it in as many places as we can.
Run pnpm lint! On the first run, the command will create caches in each workspace both at the ESLint and Turborepo layers.
Running pnpm lint (without changing any code) will give you a >>> FULL TURBO. Awesome!
Changing some code in one workspace will hit cache for all your workspaces except that specific one. That workspace will use the ESLint cache file at node_modules/.cache/.eslintcache to lint as fast as possible.
You now run your pnpm lint:fix command to see if ESLint can find any auto-fixable problems.