TypeScript Project References — Complete Guide
In this tutorial, you will learn about TypeScript Project References. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript project references let you split a large codebase into smaller projects that reference each other, enabling incremental builds, faster type checking, and clear dependency boundaries between packages.
What You'll Learn
- Composite projects and their configuration
- The
referencesfield in tsconfig - Build mode (
tsc --build) - Monorepo project structure
Why It Matters
In large codebases, a single tsconfig.json becomes a bottleneck. Every file is checked together, which is slow. Project references let TypeScript check only changed files and their dependents, dramatically reducing compilation time in monorepos.
Real-World Use
The Doda Browser extension monorepo has three projects: shared (types and utilities), extension-core (browser API), and extension-ui (React components). Each project has its own tsconfig. Shared types are compiled first, then consumed by the other two, ensuring clean separation and fast builds.
Learning Path
flowchart LR A[tsconfig Deep Dive] --> B[Project References] B --> C[Module Resolution] B --> D[You Are Here] C --> E[Source Maps] E --> F[Linting]
Composite Projects
A composite project is marked with composite: true:
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": ["src"]
}
composite: true enables declaration files and supports project references. Incremental builds use .tsbuildinfo files to track what changed.
Referencing Projects
// packages/extension-core/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" }
],
"include": ["src"]
}
Root tsconfig with References
// tsconfig.json (root of monorepo)
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/extension-core" },
{ "path": "packages/extension-ui" }
]
}
The root tsconfig has an empty files array — it exists only to orchestrate the project references.
Build Mode
Use tsc --build instead of tsc:
# Build all projects in dependency order
npx tsc --build
# Clean all build output
npx tsc --build --clean
# Force rebuild all
npx tsc --build --force
# Build with watch mode
npx tsc --build --watch
Build mode:
- Builds projects in topological order
- Skips projects that haven't changed (incremental)
- Reports errors from referenced projects
- Supports
--cleanand--force
Monorepo Structure
my-monorepo/
├── tsconfig.json # Root — orchestrates references
├── packages/
│ ├── shared/
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── dist/
│ │ └── tsconfig.json # composite: true
│ ├── extension-core/
│ │ ├── src/
│ │ │ └── index.ts # imports from shared
│ │ ├── dist/
│ │ └── tsconfig.json # references shared
│ └── extension-ui/
│ ├── src/
│ ├── dist/
│ └── tsconfig.json # references core
└── package.json
Incremental Builds
Enable incremental: true to use .tsbuildinfo files:
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo"
}
}
The .tsbuildinfo file stores compilation metadata so TypeScript only re-checks changed files on subsequent builds.
Declaration Files and Project References
When project A references project B, A needs B's .d.ts files (not .ts source). This is why composite requires declaration: true.
// packages/shared/src/types.ts
export interface User {
id: string;
name: string;
}
// packages/extension-core/src/index.ts
import { User } from "shared";
// TypeScript reads the .d.ts from shared/dist, not the .ts source
Common Mistakes
1. Not Marking Referenced Projects as composite
Referenced projects must have composite: true. Otherwise TypeScript errors.
2. Circular References
Project A cannot reference project B if B also references A (directly or transitively).
3. Missing outDir
All composite projects need outDir specified. TypeScript writes .d.ts and .tsbuildinfo to this directory.
4. Not Running tsc --build
Using plain tsc on the root won't build referenced projects. Always use tsc --build.
5. Ignoring Declaration Files
If a referenced project's .d.ts files are stale, consumers see outdated types. Always rebuild after changes.
Practice Questions
What does
composite: truedo? Enables project references and requires declaration files and outDir.How do you build all referenced projects?
npx tsc --buildat the root.What is a
.tsbuildinfofile? Stores incremental build metadata so TypeScript skips unchanged files.Can referenced projects have their own references? Yes. Dependencies are resolved transitively.
Challenge: Set up a monorepo with three packages: types (interfaces), utils (helper functions using types), and app (main application using utils). Configure project references and use tsc --build to compile.
FAQ
Mini Project: Monorepo Setup
mkdir -p monorepo/packages/{shared/src,app/src}
// monorepo/tsconfig.json
{ "files": [], "references": [{ "path": "packages/shared" }, { "path": "packages/app" }] }
// monorepo/packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"target": "ES2022",
"module": "ESNext"
},
"include": ["src"]
}
// monorepo/packages/shared/src/index.ts
export interface ScanResult {
id: string;
threats: string[];
clean: boolean;
}
// monorepo/packages/app/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"target": "ES2022",
"module": "ESNext"
},
"references": [{ "path": "../shared" }],
"include": ["src"]
}
// monorepo/packages/app/src/index.ts
import type { ScanResult } from "shared";
function processScan(result: ScanResult): void {
if (result.clean) {
console.log(`Scan ${result.id}: Clean`);
} else {
console.log(`Scan ${result.id}: ${result.threats.length} threats`);
}
}
Build: cd monorepo && npx tsc --build
What's Next
Now explore module resolution in depth:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/31-tsconfig-deep-dive" >}} | Review tsconfig |
| {{< ref "/programming-languages/typescript/33-module-resolution" >}} | Classic vs node, paths, baseUrl |
| {{< ref "/programming-languages/typescript/34-source-maps-debugging" >}} | Debugging with source maps |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro