Workspace Layout and Shared Config
Workspace Layout and Shared Config
A TypeScript monorepo is a workspace plus a config hierarchy:
package.json # workspaces: ["packages/*"]
tsconfig.base.json # strict, target, shared paths
packages/core/tsconfig.json # extends base, references: [...]
packages/web/tsconfig.json
Workspaces (npm, yarn, or pnpm) symlink package names into each other's node_modules, so import { fmt } from "@acme/core" resolves to source during development. extends gives every package the same strictness without copy-paste; each package then adds only rootDir, outDir, and its own references. Keep a single hoisted @types/node — two copies produce phantom {} mismatches between packages.
Building with Project References
Building with Project References
Mark leaf packages "composite": true and declare dependencies as references:
// packages/web/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "dist", "rootDir": "src" },
"references": [{ "path": "../core" }]
}
tsc -b walks the graph: it builds core first, emits declarations, then typechecks web against those .d.ts files instead of re-reading core sources. The incremental state lives in .tsbuildinfo, so a no-op build is near-instant — which is what makes running tsc -b in pre-commit hooks and CI practical.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Duplicate dependencies. If web installs its own React while ui installs another, two copies load and hooks break with "Invalid hook call". Enforce one version with workspace ranges ("@acme/core": "*") and a check like npm ls react.
Stale declarations. Editing core without rebuilding leaves core/dist behind; tsc -b fixes ordering, but bundlers pointed at dist still show old code.
Publishing. Reference consumers import built output, so publishable packages need files, exports, and a prepublish build.
Interview: explain why references use .d.ts outputs, how tsc -b decides a project is up to date, and how you would detect a cycle between two references.
Quiz
1. What does tsconfig "extends" actually do?
2. Why does tsc -b use each reference's declaration output instead of its sources?
3. What is the usual cause of React's "Invalid hook call" inside a monorepo?
Flashcards
Question
Workspaces
Click to reveal answer
Answer
package.json workspaces field that links local packages into node_modules for cross-package imports.
Question
tsconfig.base.json
Click to reveal answer
Answer
Shared strict compiler options; packages extend it and add only rootDir/outDir/references.
Question
Project reference
Click to reveal answer
Answer
A path dependency telling tsc -b which projects to build first.
Question
.tsbuildinfo
Click to reveal answer
Answer
Incremental state file; its presence lets tsc -b skip projects that are already up to date.
Revision Notes
Key Takeaways
- 1. Workspace linking and config extends solve different problems — resolution and strictness
- 2. tsc -b plus composite gives incremental typechecking, not bundling
- 3. One dependency graph means one version of shared peers like React and @types
Interview Tips
- • Describe the build order tsc -b derives and what happens when a reference cycle exists
- • Say how you would prove there is only one React copy in the tree
Cheat Sheet
workspaces link packages; extends shares strict options; composite + references → tsc -b builds via .d.ts + .tsbuildinfo; keep single React/@types copies.