TypeScript 73 🔷 Project References and Composite Projects
A TypeScript project starts as a single tsconfig.json and a single src directory. It grows into a monorepo with a shared library, several applications, and the test projects, and the single project’s compilation becomes the bottleneck. Every build recompiles everything, the editor’s IntelliSense slows down, and the changes to one package trigger the rebuilds of the others. Project references are TypeScript’s answer. They split the monorepo into the multiple projects, declare the dependencies between them, and let the compiler build the projects in the correct order — only the changed ones and their dependents. This chapter covers what the project references are, the composite flag, the references array, the tsc --build mode, the shared configuration, the pitfalls, and the migration from the single project.
Key point: A project reference declares that one TypeScript project depends on another. The referenced project must have composite: true, which requires the declaration: true and the rootDir‘s explicit setting, and produces the .tsbuildinfo file. The referencing project declares the dependency in the references array, and the tsc --build mode resolves the dependencies, builds them in order, and caches the results. The paths in the base config map the package names to the source directories, so the editor resolves the types from the source during the development, and the build resolves them from the .d.ts files. The project references are the monorepo’s foundation.
Why project references exist
A monorepo with one tsconfig.json has the single compilation graph. Every build recompiles every file, and the editor’s language service loads the entire project. The larger the monorepo, the slower the both.
The single project’s problems. The build is the full’s, and the full’s is the slow’s. The editor’s IntelliSense is the whole’s, and the whole’s is the memory’s. The test’s is the production’s, and the production’s is the coupling’s.
The project reference’s answer. The project reference splits the monorepo into the multiple projects, and each project has its own tsconfig.json. The references array declares the dependencies, and the tsc --build mode builds the projects in the topological’s order. The changed’s project is the only’s, and the dependent’s is the incremental’s.
Why the composite flag matters. The composite: true is the referenced project’s requirement. The flag enables the declaration: true, the rootDir‘s explicit, and the .tsbuildinfo‘s. The three are the project reference’s prerequisite.
Why the references array matters. The references array declares the dependent’s projects, and the tsc --build resolves the graph. The array’s is the path’s, and the path’s is the tsconfig.json‘s. The two are the pair, and the pair is the dependency’s.
Why the tsc --build matters. The tsc --build is the build’s mode, and the mode’s is the incremental’s. The -b is the shorthand’s, and the shorthand’s is the --build‘s. The two are the pair, and the pair is the modern’s.
Why the project references matter for the monorepo. The project references are the monorepo’s, and the monorepo’s is the multi-package’s. The single’s is the simple’s, and the simple’s is the start’s. The two are the pair, and the pair is the evolution’s.
Why the project references matter for the editor. The project references are the editor’s, and the editor’s is the IntelliSense’s. The single’s is the whole’s, and the whole’s is the slow’s. The two are the pair, and the pair is the performance’s.
Why the project references matter for the CI. The project references are the CI’s, and the CI’s is the build’s. The tsc --build is the incremental’s, and the incremental’s is the fast’s. The two are the pair, and the pair is the pipeline’s.
Why the project references are the advanced’s. The project references are the advanced’s, and the advanced’s is the monorepo’s. The single’s is the common’s, and the common’s is the start’s. The two are the pair, and the pair is the evolution’s.
The composite flag
The composite: true is the referenced project’s requirement. The flag enables the declaration: true, the rootDir‘s explicit, and the .tsbuildinfo‘s.
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
The composite: true is the flag’s, and the flag’s is the project reference’s. The declaration: true is the requirement’s, and the rootDir‘s is the source’s. The two are the pair, and the pair is the prerequisite’s.
Why the composite matters. The composite is the referenced project’s, and the referenced project’s is the declaration’s. The composite: true produces the .tsbuildinfo‘s, and the .tsbuildinfo‘s is the incremental’s. The two are the pair, and the pair is the cache’s.
Why the declaration: true is the requirement. The declaration: true is the requirement’s, and the requirement’s is the consumer’s. The consumer’s imports the .d.ts‘s, and the .d.ts‘s is the types’s. The two are the pair, and the pair is the contract’s.
Why the rootDir‘s explicit matters. The rootDir‘s explicit is the requirement’s, and the requirement’s is the composite‘s. The rootDir: "./src" is the source’s, and the source’s is the output’s. The two are the pair, and the pair is the structure’s.
Why the .tsbuildinfo matters. The .tsbuildinfo is the cache’s, and the cache’s is the incremental’s. The tsBuildInfoFile is the location’s, and the location’s is the outDir‘s. The two are the pair, and the pair is the performance’s.
Why the composite‘s constraint matters. The composite: true requires the declaration: true, and the declaration‘s is the .d.ts‘s. The requirement’s is the composite’s, and the composite’s is the project reference’s. The two are the pair, and the pair is the design’s.
Why the composite should be the referenced’s. The composite should be the referenced’s, and the referenced’s is the library’s. The app’s is the consumer’s, and the consumer’s is the leaf’s. The two are the pair, and the pair is the design’s.
Why the composite‘s include matters. The composite‘s include is the source’s, and the source’s is the project’s. The include: ["src"] is the scope’s, and the scope’s is the project’s. The two are the pair, and the pair is the design’s.
The references array
The references array declares the dependent’s projects, and the tsc --build resolves the graph.
// packages/app/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" },
{ "path": "../ui" }
],
"include": ["src"]
}
The references array is the dependent’s, and the dependent’s is the shared‘s and the ui‘s. The path is the ../shared‘s, and the ../shared‘s is the relative’s. The two are the pair, and the pair is the dependency’s.
Why the references matters. The references is the graph’s, and the graph’s is the build’s. The tsc --build resolves the references, and the references’s is the topological’s. The two are the pair, and the pair is the order’s.
Why the path‘s relative matters. The path‘s relative is the ../shared‘s, and the ../shared‘s is the directory’s. The directory’s is the tsconfig.json‘s, and the tsconfig.json‘s is the project’s. The two are the pair, and the pair is the resolution’s.
Why the references can be the deep. The references can be the deep, and the deep’s is the chain’s. The app references the shared, and the shared references the core. The two are the pair, and the pair is the graph’s.
Why the references should be the minimal. The references should be the minimal, and the minimal’s is the direct’s. The transitive’s is the indirect’s, and the indirect’s is the implicit’s. The two are the pair, and the pair is the design’s.
Why the references can be the circular’s. The references can be the circular’s, and the circular’s is the error’s. The project reference’s is the acyclic’s, and the acyclic’s is the DAG’s. The two are the pair, and the pair is the constraint’s.
Why the references‘s prepend matters. The references‘s prepend is the legacy’s, and the legacy’s is the outFile‘s. The prepend: true is the concatenation’s, and the concatenation’s is the legacy’s. The two are the pair, and the pair is the migration’s.
Why the references matters for the editor. The references is the editor’s, and the editor’s is the language service’s. The editor’s reads the references, and the references’s is the source’s. The two are the pair, and the pair is the navigation’s.
Why the references matters for the tsc --build. The references is the tsc --build‘s, and the tsc --build‘s is the graph’s. The tsc --build reads the references, and the references’s is the order’s. The two are the pair, and the pair is the build’s.
The tsc --build mode
The tsc --build is the build’s mode, and the mode’s is the incremental’s. The -b is the shorthand’s, and the shorthand’s is the --build‘s.
tsc --build
tsc -b
The tsc --build is the build’s, and the build’s is the graph’s. The -b is the shorthand’s, and the shorthand’s is the --build‘s. The two are the pair, and the pair is the modern’s.
Why the tsc --build matters. The tsc --build is the graph’s, and the graph’s is the order’s. The tsc --build resolves the references, and the references’s is the topological’s. The two are the pair, and the pair is the build’s.
The tsc --build‘s options. The --verbose is the detailed’s, the --dry is the preview’s, the --clean is the cleanup’s, the --force is the full’s.
tsc --build --verbose # the detailed's
tsc --build --dry # the preview's
tsc --build --clean # the cleanup's
tsc --build --force # the full's
The --verbose is the detailed’s, the --dry is the preview’s, the --clean is the cleanup’s, the --force is the full’s. The four are the options’s, and the options’s is the build’s. The two are the pair, and the pair is the control’s.
Why the --verbose matters. The --verbose is the detailed’s, and the detailed’s is the diagnosis’s. The --verbose shows the projects’s, and the projects’s is the order’s. The two are the pair, and the pair is the debug’s.
Why the --dry matters. The --dry is the preview’s, and the preview’s is the safe’s. The --dry shows the would-be’s, and the would-be’s is the plan’s. The two are the pair, and the pair is the test’s.
Why the --clean matters. The --clean is the cleanup’s, and the cleanup’s is the tsBuildInfo‘s. The --clean removes the outputs’s, and the outputs’s is the .tsbuildinfo‘s. The two are the pair, and the pair is the reset’s.
Why the --force matters. The --force is the full’s, and the full’s is the ignore’s. The --force ignores the .tsbuildinfo‘s, and the .tsbuildinfo‘s is the cache’s. The two are the pair, and the pair is the rebuild’s.
Why the tsc --build matters for the CI. The tsc --build is the CI’s, and the CI’s is the pipeline’s. The tsc --build is the incremental’s, and the incremental’s is the fast’s. The two are the pair, and the pair is the performance’s.
Why the tsc --build‘s watch matters. The tsc --build --watch is the watch’s, and the watch’s is the development’s. The --watch is the recompile’s, and the recompile’s is the changed’s. The two are the pair, and the pair is the productivity’s.
The shared configuration
The monorepo’s projects share the base configuration, and the extends field is the mechanism.
// tsconfig.base.json
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"composite": true
}
}
The tsconfig.base.json is the shared’s, and the shared’s is the base’s. The extends: "./tsconfig.base.json" is the child’s, and the child’s is the project’s. The two are the pair, and the pair is the monorepo’s.
Why the shared configuration matters. The shared configuration is the consistency’s, and the consistency’s is the maintenance’s. The strict: true is the base’s, and the base’s is the every project’s. The two are the pair, and the pair is the design’s.
The project’s extends. The project’s extends is the base’s, and the base’s is the shared’s.
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
The extends: "../../tsconfig.base.json" is the base’s, and the base’s is the shared’s. The rootDir and the outDir are the project’s, and the project’s is the specific’s. The two are the pair, and the pair is the inheritance’s.
Why the extends matters. The extends is the inheritance’s, and the inheritance’s is the DRY’s. The extends shares the base’s, and the base’s is the common’s. The two are the pair, and the pair is the design’s.
The paths‘s mapping. The paths maps the package names to the source’s, and the source’s is the development’s.
// tsconfig.base.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@myorg/shared": ["packages/shared/src"],
"@myorg/ui": ["packages/ui/src"]
}
}
}
The paths is the mapping’s, and the mapping’s is the source’s. The @myorg/shared is the package’s, and the package’s is the alias’s. The two are the pair, and the pair is the development’s.
Why the paths‘s mapping matters. The paths‘s mapping is the editor’s, and the editor’s is the source’s. The editor’s reads the source’s, and the source’s is the fast’s. The two are the pair, and the pair is the performance’s.
Why the paths and the references work together. The paths is the editor’s, and the references is the build’s. The editor’s resolves the source’s, and the build’s resolves the .d.ts‘s. The two are the pair, and the pair is the design’s.
Why the paths should be the source’s. The paths should be the source’s, and the source’s is the development’s. The build’s is the .d.ts‘s, and the .d.ts‘s is the references‘s. The two are the pair, and the pair is the modern’s.
The monorepo’s layout
The monorepo’s layout is the convention’s, and the convention’s is the organization’s.
monorepo/
├── package.json
├── tsconfig.base.json
├── packages/
│ ├── shared/
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── src/
│ │ └── index.ts
│ ├── ui/
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── src/
│ │ └── index.ts
│ └── app/
│ ├── package.json
│ ├── tsconfig.json
│ └── src/
│ └── index.ts
└── tsconfig.json
The monorepo/ is the root’s, and the root’s is the base’s. The packages/ is the projects’s, and the projects’s is the shared’s, the ui‘s, the app‘s. The tsconfig.base.json is the shared’s, and the shared’s is the inheritance’s. The two are the pair, and the pair is the monorepo’s.
Why the monorepo’s layout matters. The monorepo’s layout is the organization’s, and the organization’s is the maintenance’s. The packages/‘s is the convention’s, and the convention’s is the discover’s. The two are the pair, and the pair is the design’s.
The root’s tsconfig.json. The root’s tsconfig.json is the references’s, and the references’s is the all’s.
// tsconfig.json
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/ui" },
{ "path": "packages/app" }
]
}
The root’s tsconfig.json is the references’s, and the references’s is the all’s. The files: [] is the empty’s, and the empty’s is the no-source’s. The two are the pair, and the pair is the root’s.
Why the root’s tsconfig.json matters. The root’s tsconfig.json is the tsc --build‘s, and the tsc --build‘s is the entry’s. The root’s references the all’s, and the all’s is the graph’s. The two are the pair, and the pair is the build’s.
The files: []‘s purpose. The files: [] is the empty’s, and the empty’s is the no-files’s. The files: [] prevents the root’s compilation, and the root’s compilation’s is the unwanted’s. The two are the pair, and the pair is the pattern’s.
Why the files: [] matters. The files: [] is the pattern’s, and the pattern’s is the root’s. The root’s is the references’s, and the references’s is the graph’s. The two are the pair, and the pair is the build’s.
The include‘s absence. The include‘s absence is the root’s, and the root’s is the no-source’s. The include‘s presence is the project’s, and the project’s is the source’s. The two are the pair, and the pair is the distinction’s.
The pitfalls
The project references’ pitfalls are the common’s, and the common’s is the diagnosis’s.
The pitfall 1: the missing composite. The referenced project without the composite: true is the error’s, and the error’s is the Referenced project must have setting "composite": true‘s.
error TS6306: Referenced project must have setting "composite": true.
The error is the specific’s, and the specific’s is the composite‘s. The fix’s is the composite: true‘s, and the composite: true‘s is the requirement’s. The two are the pair, and the pair is the fix’s.
Why the pitfall 1 matters. The pitfall 1 is the common’s, and the common’s is the first’s. The composite: true‘s is the requirement’s, and the requirement’s is the referenced’s. The two are the pair, and the pair is the design’s.
The pitfall 2: the missing declaration. The composite: true‘s implies the declaration: true‘s, and the explicit’s is the redundant’s. The implicit’s is the automatic’s, and the automatic’s is the convenience’s. The two are the pair, and the pair is the design’s.
Why the pitfall 2 matters. The pitfall 2 is the declaration‘s, and the declaration‘s is the .d.ts‘s. The .d.ts‘s is the consumer’s, and the consumer’s is the contract’s. The two are the pair, and the pair is the design’s.
The pitfall 3: the wrong rootDir. The rootDir‘s wrong is the output’s, and the output’s is the structure’s. The rootDir: "./src"‘s is the source’s, and the source’s is the output’s. The two are the pair, and the pair is the fix’s.
Why the pitfall 3 matters. The pitfall 3 is the rootDir‘s, and the rootDir‘s is the output’s. The wrong’s is the nested’s, and the nested’s is the confusion’s. The two are the pair, and the pair is the diagnosis’s.
The pitfall 4: the missing references. The missing references is the build’s, and the build’s is the order’s. The tsc --build without the references is the incomplete’s, and the incomplete’s is the error’s. The two are the pair, and the pair is the fix’s.
Why the pitfall 4 matters. The pitfall 4 is the references‘s, and the references‘s is the graph’s. The missing’s is the no-order’s, and the no-order’s is the bug’s. The two are the pair, and the pair is the diagnosis’s.
The pitfall 5: the stale tsbuildinfo. The stale tsbuildinfo is the cache’s, and the cache’s is the stale’s. The --force‘s is the fix’s, and the fix’s is the rebuild’s. The two are the pair, and the pair is the pattern’s.
Why the pitfall 5 matters. The pitfall 5 is the cache’s, and the cache’s is the stale’s. The --force‘s is the reset’s, and the reset’s is the clean’s. The two are the pair, and the pair is the diagnosis’s.
The pitfall 6: the circular reference. The circular reference is the cycle’s, and the cycle’s is the DAG’s violation’s. The A references the B, and the B references the A. The two are the pair, and the pair is the error’s.
Why the pitfall 6 matters. The pitfall 6 is the circular’s, and the circular’s is the design’s. The cycle’s is the broken’s, and the broken’s is the refactor’s. The two are the pair, and the pair is the discipline’s.
Complete Example Session
// ============================================
// PART 1: THE BASE CONFIG
// ============================================
// tsconfig.base.json
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"composite": true,
"baseUrl": ".",
"paths": {
"@myorg/shared": ["packages/shared/src"],
"@myorg/ui": ["packages/ui/src"]
}
}
}
// ============================================
// PART 2: THE SHARED PACKAGE
// ============================================
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
// packages/shared/src/index.ts
export interface User {
id: string;
name: string;
}
export function greet(user: User): string {
return `Hello, ${user.name}`;
}
// ============================================
// PART 3: THE UI PACKAGE
// ============================================
// packages/ui/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"references": [
{ "path": "../shared" }
],
"include": ["src"]
}
// packages/ui/src/index.ts
import type { User } from '@myorg/shared';
export function renderUser(user: User): string {
return `<div>${user.name}</div>`;
}
// ============================================
// PART 4: THE APP PACKAGE
// ============================================
// packages/app/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"references": [
{ "path": "../shared" },
{ "path": "../ui" }
],
"include": ["src"]
}
// packages/app/src/index.ts
import { greet } from '@myorg/shared';
import { renderUser } from '@myorg/ui';
const user = { id: '1', name: 'Alice' };
console.log(greet(user));
console.log(renderUser(user));
// ============================================
// PART 5: THE ROOT
// ============================================
// tsconfig.json
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/ui" },
{ "path": "packages/app" }
]
}
// ============================================
// PART 6: THE BUILD
// ============================================
// tsc --build
// tsc --build --verbose
// tsc --build --dry
// tsc --build --clean
// tsc --build --force
// ============================================
// PART 7: THE OUTPUT
// ============================================
// packages/shared/dist/
// index.js
// index.d.ts
// index.d.ts.map
// tsconfig.tsbuildinfo
//
// packages/ui/dist/
// index.js
// index.d.ts
// index.d.ts.map
// tsconfig.tsbuildinfo
//
// packages/app/dist/
// index.js
// index.d.ts
// index.d.ts.map
// tsconfig.tsbuildinfo
// ============================================
// PART 8: THE INCREMENTAL
// ============================================
// 1. The change in packages/shared/src/index.ts
// 2. tsc --build
// 3. The shared rebuilds, and the ui and the app follow.
// 4. The core and the others are the untouched.
// ============================================
// PART 9: THE PITFALLS
// ============================================
// The missing composite:
// error TS6306: Referenced project must have setting "composite": true.
// The circular reference:
// error TS6202: Project references may not form a circular graph.
// ============================================
// PART 10: WHAT NOT TO DO
// ============================================
// Don't forget the composite: true
// The referenced project requires it. // ⚠️
// Don't forget the declaration: true
// The composite implies it, but the explicit is clearer. // ⚠️
// Don't forget the rootDir
// The composite requires the explicit's. // ⚠️
// Don't forget the references
// The tsc --build needs the graph. // ⚠️
// Don't use the circular's
// The references must be the DAG's. // ⚠️
// Don't forget the files: []
// The root's tsconfig must be the empty's. // ⚠️
The ten parts cover the base config, the shared package, the ui package, the app package, the root, the build, the output, the incremental, the pitfalls, and the anti-patterns.
Quick Reference
The composite‘s Requirements
| Requirement | Purpose |
|---|---|
The composite: true | The referenced project |
The declaration: true | The .d.ts files |
The rootDir‘s explicit | The output’s structure |
The .tsbuildinfo | The incremental’s cache |
The references‘s Array
| Field | Purpose |
|---|---|
The path | The relative’s tsconfig |
The prepend | The legacy’s outFile |
The tsc --build‘s Options
| Option | Purpose |
|---|---|
The --verbose | The detailed’s |
The --dry | The preview’s |
The --clean | The cleanup’s |
The --force | The full’s |
The --watch | The recompile’s |
The Shared Configuration
| File | Purpose |
|---|---|
The tsconfig.base.json | The shared’s |
The project’s tsconfig.json | The extends‘s |
The root’s tsconfig.json | The references‘s |
The Pitfalls
| Pitfall | The error |
|---|---|
The missing composite | The TS6306 |
| The circular’s | The TS6202 |
The stale tsbuildinfo | The wrong’s output |
The wrong rootDir | The nested’s output |
The Monorepo’s Layout
| The path | The purpose |
|---|---|
The tsconfig.base.json | The shared’s |
The packages/*/tsconfig.json | The project’s |
The root’s tsconfig.json | The references‘s |
The packages/*/src | The source’s |
The packages/*/dist | The output’s |
Best Practices
✅ Do This:
// Use the composite: true for the referenced
{ "compilerOptions": { "composite": true } } // ✅
// Use the references array
{ "references": [{ "path": "../shared" }] } // ✅
// Use the shared base config
{ "extends": "../../tsconfig.base.json" } // ✅
// Use the paths for the editor
{ "paths": { "@myorg/shared": ["packages/shared/src"] } } // ✅
// Use the files: [] for the root
{ "files": [], "references": [...] } // ✅
# Use the tsc --build
tsc --build # ✅
# Use the --verbose for the diagnosis
tsc --build --verbose # ✅
// Use the skipLibCheck
{ "skipLibCheck": true } // ✅
❌ Don’t Do This:
// Don't forget the composite
{ "references": [{ "path": "../shared" }] } // without it // ⚠️
// Don't forget the declaration
{ "composite": true } // the declaration's implied // ⚠️
// Don't forget the rootDir
{ "composite": true, "outDir": "./dist" } // the rootDir's // ⚠️
// Don't use the circular's
{ "references": [{ "path": "../b" }] } // the b references the a // ⚠️
// Don't forget the files: []
{ "references": [...] } // the root's compilation // ⚠️
# Don't forget the tsc --build
tsc # the single project's // ⚠️
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
The missing composite | The TS6306 | The composite: true |
The missing declaration | The no .d.ts | The declaration: true |
The missing rootDir | The nested’s output | The rootDir: "./src" |
The missing references | The no order | The references‘s |
| The circular’s | The TS6202 | The DAG’s |
The stale tsbuildinfo | The wrong’s output | The --force |
The wrong paths | The editor’s | The source’s |
The missing files: [] | The root’s compilation | The files: [] |
Real-World Examples
1. The base config
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"composite": true
}
}
2. The shared package
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "rootDir": "./src", "outDir": "./dist" },
"include": ["src"]
}
3. The ui package
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "rootDir": "./src", "outDir": "./dist" },
"references": [{ "path": "../shared" }],
"include": ["src"]
}
4. The app package
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "rootDir": "./src", "outDir": "./dist" },
"references": [{ "path": "../shared" }, { "path": "../ui" }],
"include": ["src"]
}
5. The root
{
"files": [],
"references": [
{ "path": "packages/shared" },
{ "path": "packages/ui" },
{ "path": "packages/app" }
]
}
6. The paths
{
"paths": {
"@myorg/shared": ["packages/shared/src"],
"@myorg/ui": ["packages/ui/src"]
}
}
7. The build
tsc --build
8. The verbose
tsc --build --verbose
9. The clean
tsc --build --clean
10. The force
tsc --build --force
Visual: The Project References
┌──────────────────────────────────────────────────────────┐
│ THE MONOREPO │
│ │
│ packages/app/ │
│ │ │
│ ├── references ──► packages/shared/ │
│ │ │
│ └── references ──► packages/ui/ │
│ │ │
│ └── references ──► packages/shared/│
│ │
│ THE BUILD'S ORDER │
│ 1. packages/shared │
│ 2. packages/ui │
│ 3. packages/app │
│ │
│ The tsc --build resolves the graph, and the graph's is │
│ the topological's. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The composite‘s Requirements
┌──────────────────────────────────────────────────────────┐
│ THE composite: true │
│ │ │
│ ├── The declaration: true (the implied) │
│ │ │
│ ├── The rootDir (the explicit) │
│ │ │
│ └── The .tsbuildinfo (the cache) │
│ │
│ THE REFERENCED PROJECT'S REQUIREMENTS │
│ The composite: true is the prerequisite. │
│ The declaration: true is the consumer's. │
│ The rootDir is the structure's. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The tsc --build‘s Incremental
┌──────────────────────────────────────────────────────────┐
│ THE FIRST BUILD │
│ 1. packages/shared (the full) │
│ 2. packages/ui (the full) │
│ 3. packages/app (the full) │
│ │
│ THE INCREMENTAL'S BUILD │
│ 1. The change in packages/shared │
│ 2. tsc --build │
│ 3. packages/shared (the rebuild) │
│ 4. packages/ui (the dependent's) │
│ 5. packages/app (the dependent's) │
│ 6. The other's are the untouched. │
│ │
│ The incremental's is the fast's. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The paths and the references
┌──────────────────────────────────────────────────────────┐
│ THE EDITOR (the development) │
│ The paths: │
│ @myorg/shared → the packages/shared/src │
│ The editor resolves the SOURCE's. │
│ │
├──────────────────────────────────────────────────────────┤
│ THE BUILD (the tsc --build) │
│ The references: │
│ the app → the shared, the ui │
│ The build resolves the .d.ts's. │
│ │
│ The two are the pair, and the pair is the design's. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Monorepo’s Layout
┌──────────────────────────────────────────────────────────┐
│ monorepo/ │
│ ├── package.json │
│ ├── tsconfig.base.json │
│ ├── tsconfig.json ← the references's │
│ └── packages/ │
│ ├── shared/ │
│ │ ├── tsconfig.json ← the composite's │
│ │ └── src/ │
│ ├── ui/ │
│ │ ├── tsconfig.json ← the references's │
│ │ └── src/ │
│ └── app/ │
│ ├── tsconfig.json ← the references's │
│ └── src/ │
│ │
│ The root's tsconfig is the references's, and the │
│ packages' are the projects's. │
│ │
└──────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| The project reference | The dependency’s |
The composite | The referenced’s requirement |
The declaration | The .d.ts‘s |
The rootDir | The explicit’s |
The references | The graph’s |
The tsc --build | The build’s mode |
The --verbose | The detailed’s |
The --clean | The cleanup’s |
The --force | The rebuild’s |
The extends | The shared’s |
The paths | The editor’s |
Key takeaways:
- The project references split the monorepo into the multiple projects — each project has its own
tsconfig.json, and thereferencesarray declares the dependencies - The
composite: trueis the referenced project’s requirement — it enables thedeclaration: true, therootDir‘s explicit, and the.tsbuildinfo‘s - The
referencesarray declares the dependent’s projects — thetsc --buildresolves the graph and builds the projects in the topological’s order - The
tsc --buildis the incremental’s build — it caches the results in the.tsbuildinfoand rebuilds only the changed’s projects and their dependents - The
--verbose, the--dry, the--clean, and the--forceare the options — the--verboseis the diagnosis’s, the--dryis the preview’s, the--cleanis the cleanup’s, and the--forceis the full’s - The shared configuration uses the
extends— thetsconfig.base.jsonis the shared’s, and the project’stsconfig.jsonextends it - The
pathsmap the package names to the source’s — the editor resolves the source during the development, and the build resolves the.d.tsduring the build - The monorepo’s layout is the convention’s — the
packages/directory holds the projects, and each has thesrc, thedist, and thetsconfig.json - The root’s
tsconfig.jsonis thereferences‘s — thefiles: []prevents the root’s compilation, and thereferencesdeclare the projects - The pitfalls are the missing
composite, the missingdeclaration, the wrongrootDir, the missingreferences, the circular’s, and the staletsbuildinfo— each has the specific’s error and the specific’s fix
Remember: The project references are the monorepo’s foundation. The composite is the referenced’s, and the references is the graph’s. The tsc --build is the incremental’s, and the paths is the editor’s. The shared config is the extends‘s, and the monorepo’s layout is the convention’s. The project references are the advanced’s, and the advanced’s is the scale’s.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!