![]() |
VOOZH | about |
If you have a TypeScript codebase in a monorepo that is not using TypeScript project references then you are missing a trick.
π Boost your productivity with TypeScript project referencesTypeScript project references have been around since TypeScript 3.0 and allow you to specify dependant packages in the tsconfig.json that the current package depends on. When you build a package with dependencies, then the dependencies get built first.
The tsconfig.json below specifies that this package references a common package and that the common package is built before the current package:
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"outDir": "../lib/animals",
"rootDir": ".",
},
"references": [
{ "path": "../core" }
]
}
Project references are specified via a references array of objects with a path property. The path property is a relative path to a different location containing a tsconfig.json file.
If the project were not using project references, then all packages would have to be built individually, which can be a huge drag if there are multiple dependent packages.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
The -b or --build switch is added to the tsc compiler when transpiling a package that has project references:
> tsc -b # Use the tsconfig.json in the current directory > tsc -b src # Use src/tsconfig.json > tsc -b foo/prd.tsconfig.json bar # Use foo/prd.tsconfig.json and bar/tsconfig.json
A successful build will output a tsconfig.tsbuildinfo, used in subsequent builds to ensure only new code or changed code is built for a faster build time. The tsconfig.tsbuildinfo file contains the signatures and timestamps of all files required to build the whole project. On subsequent builds, TypeScript will use that information to detect the least costly way to type check and emit changes to your project.
What has really boosted my TypeScript development is the ability to have tsc watch for changes in every package of a monorepo.
I have a script like this running the whole time I am developing:
"watch": "tsc -b ./tsconfig.packages.json --watch"
When the watch script is running, any TypeScript changes in the whole monorepo will result in a new efficient build:
In most monorepos, the individual package nodes are in a folder named packages. Below is an example monorepo with the various tsconfig.json files strategically placed with surgical precision:
tsconfig.base.json # base tsconfig.json that all packages will extend tsconfig.packages.json # tsconfig.json that is used to watch eveything all packages /package.json /packages /common # Common utitliies used by all package /src .index.ts # entry point for package /tsconfig.json # Config file for 'common' project /package.json /web # Depends on 'common' /src .index.ts # entry point for package /tsconfig.json # Config file for 'web' project /api # Depends on 'common' /src .index.ts # entry point for package /tsconfig.json # Config file for 'api' project /package.json
At the root folder is a tsconfig.base.json file that is extended by all packages in the monorepo:
{
"compilerOptions": {
"baseUrl": ".",
"composite": true,
"declaration": true,
"declarationMap": true,
"paths": {
"common": ["packages/common/src"],
"web": ["packages/web/src"],
"api": ["packages/api/src"]
}
}
}
There is actually a lot going on here:
"composite": true instructs tsc that there are project references"declarationMap": true adds source maps for .d.ts declaration files. It means Go to Definition in VS code and will go to the actual file and not the .d.ts filepaths field contains an object with each object key (common, web, or API in this example) pointing to a packageβs location. With this set, we can import packages without a nasty relative or absolute path.import { a } from 'web';In this example, each package extends tsconfig.base.json using the extends option of tsconfig:
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
},
"references": [
{ "path": "../core" }
]
}
The tsconfig.packages.json at the root of the project structure references all packages that we want to watch or build:
{
"files": [],
"references": [
{
"path": "./packages/common"
},
{
"path": "./packages/web"
},
{
"path": "./packages/api"
}
]
}
With this structure in place, we can now build all packages in one swoop:
tsc -b ./tsconfig.packages.json
or watch:
tsc -b ./tsconfig.packages.json
The following switches are handy when working with project references:
--verbose: Prints out verbose logging to explain what is going on (may be combined with any other flag) --dry: Shows what would be done but does not actually build anything --clean: Deletes the outputs of the specified projects (may be combined with --dry) --force: Acts as if all projects are out of date --watch: Watch mode (may not be combined with any flag except --verbose)
Bundlers like webpack and Rollup did not offer support for project references for quite some time, and Rollup only has partial support. This has not helped adoption.
When configuring the webpack loader ts-loader, you need to set the projectReferences option to true:
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: {
projectReferences: true,
},
},
},
I do most of my main development in monorepoβs these days, both in client projects and my own personal development. If I use TypeScript as my main language, then I will enable project references just like I have outlined here. I find them a great productivity boost that not everyone is using.
LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings β compatible with all frameworks, and with plugins to log additional context from Redux, Vuex, and @ngrx/store.
With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.
Modernize how you understand your web and mobile apps β start monitoring for free.
TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension β no new framework required.
Learn how to build a full React Native auth system using Better Auth and Expo β with email/password login, Google OAuth, session persistence, and protected routes.
Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.
Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.
Hey there, want to help make our blog better?
Join LogRocketβs Content Advisory Board. Youβll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now