TypeScript
Completed
-
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
216: API Extractor for Public API Surfaces
I've been working on this utility library called coin-core—basically a set of tools for handling currency conversions and transaction validation. I just ran tsc to build it, and I'm looking at my dist folder. It's a disaster. I have a mirror image of my src folder, but every .ts file has been turned into a .d.ts file. If a user wants to see what's available in my library, they have to hunt through six different folders of declaration files.
The "Dist Folder" Headache
Here is the current state of my src/index.ts. It looks clean enough:
export * from './models/transaction';
export * from './services/converter';
export * from './utils/internal-validator'; // Oops.
Wait, I just noticed something. I'm exporting internal-validator because I needed it for some integration tests, but I absolutely do not want a library consumer importing InternalValidator and building their entire app around it. If I change a private method in that validator next week, I'll be breaking a thousand downstream projects. In a standard TypeScript build, if it's exported, it's public. Period.
I tried just removing the export, but then my own internal service modules couldn't reach it. I'm stuck in that classic library-author dilemma: I need it exported for the build to work, but I want it hidden from the user.
Bringing in API Extractor
I've heard that Microsoft uses @microsoft/api-extractor for their big projects, so I figured it's time to see if it actually helps. I'll start by installing it and setting up the config. I'm going to create an api-extractor.json file in the root.
{
"mainEntryPointFilePath": "./dist/index.d.ts",
"recourses": [],
"analyzed源代码Path": "./src/index.ts"
}
I run api-extractor run and... it immediately screams at me. It didn't just fail; it gave me a list of "unannotated" exports. It's telling me that it has no idea which of my exports are intended to be part of the public API and which are just... there. It's forcing me to be intentional.
Taming the Exports with @public
This is where the workflow changes. API Extractor doesn't rely on the export keyword alone; it looks for JSDoc tags. I'll go back into my transaction.ts and actually mark the stuff I'm proud of.
/**
* @public
*/
export class Transaction {
constructor(public readonly amount: number, public readonly currency: string) {}
}
/**
* @internal
*/
export class TransactionInternalHelper {
// This should stay hidden from the user
static validateChecksum(data: any) { return true; }
}
I do this for the rest of the files, marking the core services as @public and the validators as @internal. Now, when I run api-extractor run again, the magic happens. Instead of a folder full of .d.ts files, it generates a single, consolidated index.d.ts (often called a "rollup").
I'm looking at the output now. The TransactionInternalHelper is completely gone. It exists in the dist folder for the runtime, but it's stripped out of the public declaration file. This is huge. I can now change the internal helper however I want, and as long as I don't touch the @public classes, my users will never know.
The "Rollup" Realization
I noticed something interesting while scrolling through the generated file. API Extractor didn't just delete the internal stuff; it flattened the structure. Instead of the user seeing import { Transaction } from 'coin-core/dist/models/transaction', they just see import { Transaction } from 'coin-core'.
It basically took my fragmented file system and turned it into a clean, professional API surface. If I accidentally export something without a tag, the tool warns me during the build process. It's essentially acting as a "linter" for my public-facing contract. I'm no longer just hoping the user doesn't import the wrong thing; I'm explicitly defining the boundary of my library.
📋 Practical Task
Exercise: Hardening the "Secure-Vault" API Surface
You are maintaining a library called secure-vault. Currently, the library is leaking internal encryption logic to the end-user because everything is exported. Your goal is to use API Extractor concepts to lock down the public surface.
Scenario:
You have two files: vault.ts and crypto-utils.ts.
Vaultclass: Should be public.encryptDatafunction: Should be public.ROT13Fallbackclass: Used internally for legacy support; must be internal.debugLogInternalStatefunction: Used for developer logging; must be internal.
Your Task:
1. Write the JSDoc tags (@public or @internal) for the four members listed above.
2. Explain why simply removing the export keyword from ROT13Fallback might break the internal build if Vault depends on it.
3. Describe what the final .d.ts file produced by API Extractor will contain regarding these four members.
There are no comments for now.