Configure TypeScript monorepo with Turborepo, ESLint, Prettier, and Webstorm.
Nowadays, software development for the web is becoming increasingly broken down, given the restrictions of technology and expertise. They may not be produced in the same language — even if it is the same language the frameworks won’t match up; the code is built differently and deployed to different targets; plus, there can be separate teams dealing with frontend and backend. Even though we cannot do anything with regard to the technologies and fragmentation, we can mitigate the complexity for cross-functional teams and collaborative development — monorepos to save the day!
When it comes to choosing the right tools for a JavaScript or TypeScript monorepo, there is a difficult decision to be made as the options are vast. We’ve got Yarn Workspaces, NPM Workspaces, NX, Lerna, and now Turborepo. We have previously explored how to set up a TypeScript monorepo with WebStorm, ESLint, and Prettier using Yarn Workspaces. Today we’ll do exactly the same with Turborepo, which has become my preferred tool for a monorepo setup.
I intend to illustrate how setting up a monorepo with TypeScript has become simpler by following precisely the same structure as my previous article.
What is Turborepo?
It’s easier to say what it’s not: it’s definitely not a package manager, it works with npm, pnpm, and yarn, although they recommend pnpm if you can’t choose yourself.
Turborepo is a new kid in town, created more or less 1–2 years ago and acquired by Vercel, it is one of the best developer tools for JavaScript projects. It is written in Rust, it’s incredibly fast, and it removes lots of complexity of the monorepo management.
Besides managing multiple packages in a monorepo, it’s also a high-performance build tool and has a Turbopack, a high-performance successor to Webpack.
All in all, it sounds very promising; it is also backed by a company with a $3B valuation, so it’s not going to disappear any time soon. Let’s go and test it!
Installing Turborepo
To start with, let’s install Turborepo. As we will be using pnpm, let’s make sure that it’s set up too. I am going to use brew install pnpm
; it can also be installed via npm install pnpm -g
. If none of those options suit your needs, here are installation instructions for other platforms: https://pnpm.io/installation.
After that, launch npx create-turbo@latest
, enter the name of the directory in which you’d like to init the repo, and turborepo will initiate the source code of the monorepo.
Directory structure
Let’s explore the folder structure of the entire project.
We have two main directories here in the project root:
- apps — source files of our applications;
- packages — re-usable code goes here and some config files, which we’ll see later.
Note: it is only a convention to use these two directories, on larger projects you can customize your monorepo setup to include more by editing the pnpm-workspace.yaml
configuration file (we’ll see that later too).
If we’ll go into packages/ui
, we’ll find a typical React package structure with package.json
, tsconfig.json
, and main index.tsx
file and Button.tsx
component.
But the most exciting part is if we’ll go to one of the applications, for example, to apps/web/pages/index.tsx
:
We see that we can import a local package ui without any problems! The import path looks great, as well, as it doesn’t include the absolute or relative paths to the packages. Out of the box, we’ve got something that had to be configured not that long ago.
TypeScript configuration
Let’s examine how to set up TypeScript. Different packages require varying tsconfig settings, and we should be able to make that happen.
There’s a packages/tsconfig
package, which looks like a kinda obvious place to look at, but we’ll reverse-engineer a little bit and look at apps/web/tsconfig.json
instead:
We can see that it extends tsconfig/nextjs.json
and it’s interesting just how exactly we are referring to that file. Here’s how package.json
looks like:
So, we are importing a package called “tsconfig” which we are then using to extend the typescript configuration.
Finally, let’s see what do we got in the packages/tsconfig
:
We’ve got a base.json
with a default configuration, which is then extended by nextjs.json
and react-library.json
configs. All of those are exported in packages.json
.
This structure is very versatile, as you can group TypeScript configs in a package which can then be imported and extended by other packages or apps.
So far, zero configuration is required to make anything work in WebStorm. Everything works like a charm out of the box.
Prettier and ESLint configuration
Finally, we can configure ESLint and Prettier. If anything needs to be configured, of course. To this point, nothing else has needed to be set up. Let’s check the source files in packages/eslint-config-custom
:
We’ve got a custom ESLint config that is exported from index.js. Let’s find out how it can be used in other apps and packages.
Looking at apps/web/.eslintrc.js
:
tells us that we are just extending the custom ESLint config we defined in the package. I’m guessing that we also have to import that package in our package.json
:
And yes, we are importing our local package from the workspace. There is, however, some under-the-hood magic going on, as we are importing eslint-config-custom
but we are extending the custom config in .eslintrc.json
. I suppose it’s an internal Turborepo notation that’s going to take the suffix out of eslint-config-*
, so theoretically, we might have the possibility to maintain multiple ESLint configurations.
Again, ESLint works perfectly without any additional configuration in WebStorm.
Additional configuration
Let’s explore a little bit further and check the contents of turbo.json
and pnpm-workspace.yaml
:
So, on the left, in pnpm-workspace.yaml
, we are defining the structure of our monorepo. It shows that we can easily extend the default convention and have more than two root-level directories if needed.
On the right, in turbo.json
, we are defining our tasks, which, according to documentation, once registered in turbo.json
can then be run with turbo run <task name>
, for example turbo run build
.
That’s exactly what we’ll see if we’ll open the root-level package.json
:
Final steps
We haven’t needed to make any extra changes in the IDE — Turborepo’s project structure gives us what we need, and that means we can start working right away.
Turborepo offers much more than just monorepo management; it also includes a build system, CI/CD integration, task caching, and other features which we aren’t discussing here. What we have seen in this article is barely scratching the surface, really, but it’s incredible how fast and easy the configuration is. In fact, we don’t even need any configuration and can start working on our project immediately, as Turborepo provides some really sane defaults.
In 3 clicks or keystrokes, we get all professional tools for JS/TS projects: TypeScript is supported by default, Prettier is there to guarantee consistent code style across the packages, and ESLint is pre-configured. The default folder structure is good, but it can be tweaked for larger projects. We can publish npm packages if needed or keep them private. There is a possibility to integrate with other tools to manage package versions or to use GitHub changesets.
My conclusion is that I see zero to no reasons to use Lerna or Yarn workspaces for most of my current projects from now on, and I’m really excited to see further improvement in the quality of professional tools in JavaScript/TypeScript space.