Skip to Content
Course content

216: API Extractor for Public API Surfaces

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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.

  • Vault class: Should be public.
  • encryptData function: Should be public.
  • ROT13Fallback class: Used internally for legacy support; must be internal.
  • debugLogInternalState function: 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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.