You likely don't need TurboRepo
3 Ways to Configure TypeScript in a Monorepo
There are mainly 3 ways to configure TypeScript in a monorepo:
1. Single tsconfig.json for the Entire Monorepo
This approach involves maintaining a single tsconfig.json file at the root of your project. It’s simple but has significant downsides:
- Pros: Easy to set up, consistent configuration across all packages.
- Cons: Performance becomes a major issue as the monorepo grows. TypeScript’s type-checking and build process slow down dramatically with a large number of files.
2. Separate tsconfig.json for Each Package
In this setup, each package in the monorepo has its own tsconfig.json file. Shared configurations are managed through a base tsconfig.base.json that individual package configurations extend. This setup strikes a balance:
- Pros: Faster builds since packages are isolated. Provides flexibility to customize configurations per package.
- Cons: Still lacks native support for cross-package dependencies and incremental builds.
Disadvantages are usually mitigated by combining with a monorepo tool like Lerna, Nx, or Turborepo to handle dependency graphs.
3. TypeScript Project References
Project References are TypeScript’s solution for managing dependencies between packages in a monorepo. Each package has its own tsconfig.json, which includes references to other packages it depends on. This setup allows for incremental builds and cross-package type-checking:
- Pros: Incremental builds, efficient type-checking across packages.
- Cons: One common criticism of Project References is the need to manually replicate the project’s architecture in the configuration file (which can be tedious). However, the TypeScript team is exploring ways to improve this (give the issue a thumbs up! 👍🏼).
Comments on strategies
Actually, Jarred Palmer proposes a fourth strategy in his article "You might not need TypeScript project references". I also found this article where a GitHub user goes into more detail about this approach. The reason I don't mention it among the options above is because I don't see any difference from using a monolith (i.e., referencing relative paths instead of dependencies and using a single tsconfig). So I don't see much point in it.
Back to our initial 3 options, a single tsconfig.json simply isn’t viable in large monorepos due to performance issues. That leaves options 2 and 3 as the most practical approaches.
TurboRepo vs. Project References
I don't actually have a preference for either of the 2 strategies yet. The title of the article comes from the section in the TurboRepo documentation: You likely don't need TypeScript Project References, and not so much about the article I linked earlier from Jarred Palmer (that's why I didn't title it "You might not need TurboRepo").
However, I'm starting to see a trend where some tools are following in TypeScript's footsteps by introducing options to optimize large monorepos (especially ones that require watch mode).
One example is Vitest with its workspaces option (which since the v3.0 release this week can now be put into the main configuration file).
Anyway, part of the reason I wrote this article was to hear opinions about these two alternatives. What do you prefer, using TurboRepo or TS Project References? Let me know!