-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
121: Build Caching Strategies for TypeScript Monorepos
Look, I've seen this a dozen times in growing engineering teams: the build times start creeping up, the CI pipeline becomes a bottleneck, and someone suggests, "We just need to turn on incremental: true in our tsconfig.json." They think that because TypeScript can remember what it did last time, the monorepo problem is solved. It isn't.
Thinking incremental: true is a Silver Bullet
Here is why that line of thinking fails you in a real monorepo. The incremental flag creates a .tsbuildinfo file. This file is great for your local machine because it tells the compiler, "Hey, I already checked this file, and nothing changed." But in a modern CI/CD environment, every build usually starts on a fresh container. That .tsbuildinfo file vanishes. You're essentially paying the overhead of writing the cache file without ever actually reading it back in.
Even worse, if you try to commit that build info file to Git (please, don't do this), you'll end up in "merge conflict hell" because the file is a binary-ish blob that changes every time anyone saves a file. You aren't caching the result of the work; you're just caching the map of the work, and that map is useless if it's not persisted across your entire team's environment.
Shifting to Content-Addressable Task Caching
To actually move the needle, you have to stop thinking about "compiler settings" and start thinking about "task outputs." In a monorepo—say you have a @company/ui-kit, a @company/api-types, and a @company/web-app—you want to treat each package as a pure function. The inputs are the source files and the dependencies; the output is the dist folder.
The correct strategy is Content-Addressable Caching. Instead of relying on a timestamp or a local TS file, you hash the contents of the package folder. If the hash of @company/ui-kit hasn't changed since the last successful build, you don't run tsc at all. You simply pull the dist folder from a remote cache (like S3 or a dedicated tool like Nx or Turborepo) and move on. This turns a 10-minute build into a 10-second "restore" operation.
// Conceptual logic for a cached build task: const hash = calculateHash(['src/**/*.ts', 'package.json', 'tsconfig.json']); if (remoteCache.exists(hash)) { remoteCache.download(hash, './dist'); console.log("Cache hit! Skipping TypeScript compilation."); } else { execSync('tsc -b'); // Use build mode for project references remoteCache.upload(hash, './dist'); }Avoiding the "Ghost Cache" Trap with Proper Input Definitions
Now, here is where most people trip up once they implement the above: they forget about global dependencies. I once spent an entire afternoon debugging a production bug because we were caching our
@company/api-typespackage based only on its own folder. But that package depended on a globaltypescriptversion defined in the rootpackage.json. When we upgraded TS from 4.8 to 5.0, the hash for the package didn't change, so the cache kept serving us binaries built with the old version of the compiler.To avoid this "ghost cache" effect, your cache key must include:
- The source code of the package.
- The
package.jsonof the package. - The
tsconfig.jsonof the package. - The
package-lock.jsonoryarn.lockfrom the root. - The version of the build tool (e.g., the version of
tsc).
📋 Practical Task
Implement a Hashed Cache Pipeline for the shared-auth Package
You have a monorepo where the packages/shared-auth library is causing slow CI runs. Currently, the CI runs tsc on every single commit, regardless of whether the auth logic changed.
Your Task: Write a Node.js script (cache-build.js) that implements the content-addressable strategy discussed in the lesson. The script should:
- Generate a SHA-256 hash based on the contents of
packages/shared-auth/src,packages/shared-auth/package.json, and the rootpackage-lock.json. - Check a local directory
.build-cache/to see if a folder named after that hash already exists. - If it exists, copy the contents of that cached folder into
packages/shared-auth/distand exit. - If it doesn't exist, execute
tsc -p packages/shared-auth/tsconfig.json, and then copy the resultingdistfolder into.build-cache/[hash]for future use.
Note: You can use the crypto module for hashing and fs-extra or fs for file operations.
There are no comments for now.