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
48: Declaration Files (.d.ts)
A few years ago, I was tasked with migrating a massive legacy project to TypeScript. The codebase relied on a very specific, niche JavaScript library for handling complex date calculations that hadn't been updated since 2014. The moment I changed my file extensions to .ts, my editor turned into a sea of red squiggles. TypeScript had no idea what this library was, what functions it exported, or what arguments those functions took. I found myself constantly using //@ts-ignore just to get the project to compile, which felt like cheating—and it was. I was effectively writing JavaScript in a TypeScript file, losing all the safety we'd worked so hard to implement.
That's when I really dove into declaration files. A .d.ts file is essentially a "map" for your code. It doesn't contain any actual logic—no function bodies, no variable assignments—just the types. It tells the TypeScript compiler: "I know this code exists in JavaScript at runtime, and here is exactly what the shapes of those objects and functions look like."
Mapping JavaScript to TypeScript
When you install a popular library like React or Lodash, you'll often see a separate package like @types/react. Those are declaration files maintained by the community via DefinitelyTyped. But when you're dealing with a private internal library or an obscure package, you have to write these yourself.
If you have a JavaScript file called utils.js that exports a function to format currency, TypeScript sees it as any. To fix this, you create a utils.d.ts file in the same directory. You aren't rewriting the logic; you're just describing the interface. It looks like this:
// utils.d.ts
export declare function formatCurrency(amount: number, currencyCode: string): string;
export declare const DEFAULT_LOCALE: string;
Notice the declare keyword. This is the secret sauce. It tells TypeScript that the implementation is defined elsewhere (in the JS file) and it should just trust the type definition provided here. Once this file is in place, the red squiggles vanish, and you get full IntelliSense for formatCurrency across your entire project.
Ambient Declarations and the Global Scope
Sometimes you aren't dealing with a module you import, but rather something that just "exists" in the environment—like a global variable injected by a <script> tag in your HTML or a Node.js global. If you try to use a global variable like window.myCustomAnalytics, TypeScript will complain that the property doesn't exist on the Window type.
To solve this, we use "ambient declarations." By using declare global or simply declaring a variable outside of a module (a file without import or export statements), you can augment the global scope. I've found this incredibly useful when integrating third-party tracking pixels or legacy CMS variables.
// globals.d.ts
declare global {
interface Window {
myCustomAnalytics: {
trackEvent: (name: string, data: object) => void;
userId: string;
};
}
}
export {}; // This ensures the file is treated as a module
I added the export {} at the bottom because, in TypeScript, a file without an import or export is treated as a script that defines things in the global scope. Adding the empty export forces it to be a module, which is often necessary when you're specifically using declare global to avoid naming collisions and ensure the compiler handles the augmentation correctly.
📋 Practical Task
Creating Type Definitions for the "SuperChart" Legacy Library
You are working with a legacy JavaScript library called super-chart.js. It is not written in TypeScript and has no @types package. The library provides a global object called SuperChart that is attached to the window object.
The library has the following functionality:
- A method
render(elementId: string, data: number[])that returns abooleanindicating if the render was successful. - A method
setTheme(theme: 'light' | 'dark')that returns nothing. - A property
versionwhich is astring.
Your Goal: Create a declaration file named superchart.d.ts that uses declare global to augment the Window interface. Ensure that window.SuperChart is fully typed so that you can call its methods with autocomplete and type safety in your main TypeScript files.
There are no comments for now.