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!

Prettier

Prettier is a great formatter for quickly taking care of simple, syntax consistency in your codebase. You can automatically standardize your entire codebase to have the same stylistic qualities and never worry about it again.

Setting up Prettier

Prettier has one simple job: standardize syntax. That simplicity can be reflected in the way that we set up Prettier since there isn't a need for overriding configurations in workspaces.

We will still be able to format our workspaces as their own separate tasks to take advantage of caching but we will only have one file in our repository for establishing style.

Add Prettier to your repo

In your root package.json, you'll need to do two things:

  1. Install prettier (and prettier-plugin-packagejson if you'd like to format those).
  2. Add your formatting scripts.
./package.json
{
"name": "my-project",
"version": "0.1.0",
"scripts": {
"format": "prettier . \"!apps/** !packages/** !tooling/**\" --check --cache --cache-location='node_modules/.cache/.prettiercache'",
"format:fix": "prettier . \"!apps/** !packages/** !tooling/**\" --write --cache --cache-location='node_modules/.cache/.prettiercache' --log-level=warn"
},
"devDependencies": {
"prettier": "^3.0.3",
"prettier-plugin-packagejson": "^2.4.3",
"turbo": "^1.10.13"
}
}
./package.json
{
"name": "my-project",
"version": "0.1.0",
"scripts": {
"format": "prettier . \"!apps/** !packages/** !tooling/**\" --check --cache --cache-location='node_modules/.cache/.prettiercache'",
"format:fix": "prettier . \"!apps/** !packages/** !tooling/**\" --write --cache --cache-location='node_modules/.cache/.prettiercache' --log-level=warn"
},
"devDependencies": {
"prettier": "^3.0.3",
"prettier-plugin-packagejson": "^2.4.3",
"turbo": "^1.10.13"
}
}

Note: Prettier ^3.0.0 currently breaks the VSCode plugin for monorepos. We're sticking with ^2.8.0 for now.

We're including some flags on these commands so our tasks go as fast as possible. You can learn more about them in the Prettier CLI documentation but, to make a long story short:

Create a Prettier configuration file

In the root of your project, we'll write our rules for the repository:

./.prettierrc.js
// Purely for demonstration! Adjust to your liking.
/** @type {import("prettier").Options} */
const config = {
tabWidth: 2,
semi: false,
singleQuote: true,
};

export default config;
./.prettierrc.js
// Purely for demonstration! Adjust to your liking.
/** @type {import("prettier").Options} */
const config = {
tabWidth: 2,
semi: false,
singleQuote: true,
};

export default config;

Both our root and workspace Prettier scripts will end up using this configuration.

Add to your workspaces

Let's get our workspaces formatted by adding scripts for these tasks. These are the same ones from the root.

packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"scripts": {
"format": "prettier . --check --cache --cache-location='node_modules/.cache/.prettiercache'",
"format:fix": "prettier . --write --cache --cache-location='node_modules/.cache/.prettiercache' --log-level=warn"
}
}
packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"scripts": {
"format": "prettier . --check --cache --cache-location='node_modules/.cache/.prettiercache'",
"format:fix": "prettier . --write --cache --cache-location='node_modules/.cache/.prettiercache' --log-level=warn"
}
}

Ignoring code in workspace tasks

Prettier does not look above it's execution context for ignore files. This means that you'll need to create a .prettierignore file for each of your workspaces.

You will always want to ignore formatting for the output directory of your builds. For instance, in a workspace for a Next.js app, you'll want to add an ignore file like this one:

apps/web/.prettierignore
.env*
.next
apps/web/.prettierignore
.env*
.next

Write pipelines

Once we've created our formatting scripts in any workspaces that we want to format, it's time to build up our Turborepo pipelines.

turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"//#format": {
"outputs": ["node_modules/.cache/.prettiercache"]
},
"format": {
"dependsOn": ["^topo"],
"outputs": ["node_modules/.cache/.prettiercache"]
},
"//#format:fix": {
"outputs": ["node_modules/.cache/.prettiercache"],
"cache": false
},
"format:fix": {
"dependsOn": ["^topo"],
"outputs": ["node_modules/.cache/.prettiercache"],
"cache": false
}
}
}
turbo.json
{
"pipeline": {
"topo": {
"dependsOn": ["^topo"]
},
"//#format": {
"outputs": ["node_modules/.cache/.prettiercache"]
},
"format": {
"dependsOn": ["^topo"],
"outputs": ["node_modules/.cache/.prettiercache"]
},
"//#format:fix": {
"outputs": ["node_modules/.cache/.prettiercache"],
"cache": false
},
"format:fix": {
"dependsOn": ["^topo"],
"outputs": ["node_modules/.cache/.prettiercache"],
"cache": false
}
}
}

Run your format tasks

With all of that ready to go, we're now ready to run our tasks!

In the root of our monorepo, we will create these scripts:

package.json
{
"scripts": {
"format": "turbo format --continue",
"format:fix": "turbo format:fix --continue"
}
}
package.json
{
"scripts": {
"format": "turbo format --continue",
"format:fix": "turbo format:fix --continue"
}
}

Note: --continue runs the rest of your tasks even if one fails.

Run pnpm format! On the first run, the command will create caches in each workspace both at the Prettier and Turborepo layers.

With this all in place, you can run your linting tasks with incredible speed.