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
86: Understanding Declaration Emit
If you've been writing TypeScript for a while, you're used to the compiler handling everything in one go: you write TS, it checks the types, and it spits out JS. But the moment you move from writing an application to writing a library that other people (or other teams) will consume, you hit a wall. I remember the first time I published a small internal utility for my team; I compiled my code to JavaScript, pushed it to our registry, and then watched my teammates spend an hour wondering why their IDEs were marking everything as any.
Shipping JavaScript and hoping for the best
The naive way to handle a library is to treat it like a plain JavaScript project. You run tsc, it generates your .js files, and you ship those. To the JavaScript runtime, this is perfect. The code runs exactly as expected. But to the TypeScript compiler in the consumer's project, your library is a black box. There are no type signatures, no interface definitions, and no hints about what the functions actually expect.
// In the library:
export function convertCurrency(amount: number, rate: number): number {
return amount * rate;
}
// After naive compilation (only .js is shipped):
// export function convertCurrency(amount, rate) { return amount * rate; }
When your colleague imports convertCurrency, TypeScript has no way of knowing that amount should be a number. It defaults to any. At this point, you've essentially stripped away the primary reason for using TypeScript in the first place. You might be tempted to tell your team to just "cast it" or write their own .d.ts file to wrap your library, but that's a maintenance nightmare. You're asking your users to do your job for you.
Letting the compiler do the paperwork
The professional way to handle this is through Declaration Emit. Instead of just outputting the executable JavaScript, you tell TypeScript to also generate "Declaration Files" (.d.ts). These files contain only the type information—no actual logic—which acts as a map for any TypeScript project consuming your code.
You do this by adding "declaration": true to your tsconfig.json. When you run your build now, the compiler doesn't just give you index.js; it gives you index.d.ts. This file is the "contract" of your library. It tells the consumer: "I promise that convertCurrency takes two numbers and returns a number," without requiring them to compile your original source code.
// The generated index.d.ts (simplified)
export declare function convertCurrency(amount: number, rate: number): number;
Now, the experience is seamless. The consumer gets full autocomplete, type checking, and jump-to-definition capabilities, while the runtime only ever touches the lean JavaScript files. I've found that this is the single biggest difference between a "script" and a "package."
The cost of the extra files
Is there a catch? Not really, but there is a trade-off. Enabling declaration emit increases your build time slightly because the compiler has to perform a second pass to extract the type signatures. In massive projects with thousands of files, this can become noticeable. You might also find your dist folder looking a bit cluttered with twice as many files as before.
However, that's a small price to pay for type safety. If you're building a library, shipping .d.ts files isn't optional—it's the standard. If you don't provide them, you're essentially forcing your users to downgrade to JavaScript the moment they import your package. I'd rather wait an extra ten seconds for my build to finish than deal with a Slack message from a frustrated dev asking why their types are missing.
📋 Practical Task
Configuring a Typed String-Manipulation Library
You are building a utility library called text-toolkit. Your goal is to ensure that anyone who installs this library gets full type support without having to see your original TypeScript source code.
Requirements:
- Create a file named
index.tswith a functiontruncate(str: string, length: number): stringand an exported interfaceToolkitConfig. - Create a
tsconfig.jsonfile. - Configure the
tsconfig.jsonso that the compiler generates both the JavaScript output AND the declaration files (.d.ts). - Ensure the output is directed to a folder named
dist.
Verification: Run tsc in your terminal. Check your dist folder; it should contain both index.js and index.d.ts. If index.d.ts is missing, your configuration is incorrect.
There are no comments for now.