This is a demo of four different ways of setting up a TypeScript monorepo.
Demos available:
main
: TypeScript project references and built package (web vs web2)project-references
: Only project referencesbuilt-packages
: Only built packagesinternal-packages
: Exporting typescript source code in package.json, no build steppath-aliases
: Using path aliases from the roottsconfig.json
file.
Winner based on LSP speed (no surprises): built-packages
On the main
branch, the monorepo is setup using TypeScript project references, however the web2
app escapes out of this setup by
not referencing the api
package to simulate the built packages way of doing things.
The api
package is a trpc api that has 100 routers, with each router having a child router to simulate a typescript lsp heavy codebase.
The test concludes the following:
web
app with project references is slower to calculate auto-completions compared toweb2
app with no project references.web
has a better DX because of live types, butweb2
can scale better with more TypeScript heavy stuff going on.web2
is faster to calculate auto-completions compared toweb
because it resolves types using thedist
folder instead of the source code.
Advantages of project references:
- Live types, you don't need to build everything to resolve dependencies.
- Better for CI/CD for typechecking, linting...etc because you don't need to build before running these commands.
- You can use
tsc --build
from the root of the monorepo to build everything at once. tsc
knows the linkage and hierarchy of packages to each other, thus only recompiles what has changed.- Go to definition is more accurate because it uses the source code.
Advantages of built packages:
- Immediate auto-completion from the moment you build the packages.
- Scales better because you don't need to worry about your LSP getting slower as the monorepo grows.
- Go to the
trpc.tsx
file in bothweb
andweb2
apps after runningbun tsc --build --verbose
at the root of the project. - Below this line:
export const trpc = createTRPCReact<AppRouter>()
, try to access something from the trpc object, like type:
trpc.
Observe how long it takes for the auto-completion to pop up.
I personally think that in the world of monorepos, slow LSP is a huge issue and it should be prioritized, it lowers developer morale and productivity.
You can use a combination of built packages and task management tools like turbo
or nx
to build your packages in order.
You can also build your packages on postinstall
to ensure types are resolved at install time.
Project references are cool but they are not a true fix for slow LSP, they are are a middle ground, theoretically they should be faster than just exporting the source code because of typescript understanding the hierarchy of the code, however they are still not as fast as built packages.
Go to definition being off in built packages is a TypeScript bug which should be fixed sometime in the future hopefully.