-
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
114: Writing Your Own .d.ts for an Untyped Library
A few years ago, I joined a project that relied on a niche JavaScript library for handling complex geospatial calculations. It was a brilliant piece of engineering, but it was written in 2012 and had absolutely no type definitions. For the first two weeks, I felt like I was coding with a blindfold on. Every time I called a function, TypeScript just gave me an any, and I found myself constantly jumping back and forth between my code and the original library's source files just to check if a parameter was supposed to be an array of numbers or a single coordinate object. I spent more time reading the implementation than actually writing features.
When you hit this wall, you have two choices: stay in any land or build your own declaration file. Since the community hasn't provided a @types package for every single library on NPM, knowing how to write your own .d.ts file is a superpower. It allows you to "teach" TypeScript how a JavaScript library works without actually touching the library's source code.
Defining the Module Boundary
To start, you need to tell TypeScript that this library exists as a module. We do this using the declare module syntax. Imagine we're dealing with a hypothetical library called fancy-canvas-utils that provides some helper functions for drawing. You'd create a file—usually named fancy-canvas-utils.d.ts—and wrap everything in a module declaration.
declare module 'fancy-canvas-utils' {
export function drawCircle(x: number, y: number, radius: number): void;
export function clearCanvas(): void;
}
At this point, the any warnings vanish. You've essentially created a "contract" that tells the compiler, "Trust me, when I import from this package, these functions exist and they take these specific types." I usually start by mapping out the most common functions first, rather than trying to document the entire library in one sitting. There's no rule saying your declaration file has to be 100% complete on day one.
Tightening the Type Definitions
Once you have the basic functions mapped, you'll likely find that the library uses complex objects. Using any inside your .d.ts file defeats the purpose of doing this in the first place. Instead, define interfaces that represent the library's internal data structures. Let's say fancy-canvas-utils has a CanvasConfig object that controls the render quality.
declare module 'fancy-canvas-utils' {
export interface CanvasConfig {
antialias: boolean;
resolution: number;
backgroundColor?: string; // Optional property
}
export function initCanvas(config: CanvasConfig): void;
export function drawCircle(x: number, y: number, radius: number): void;
export function clearCanvas(): void;
}
I've found that adding optional properties (using the ?) is critical here. Many JS libraries have "options" objects where only one or two keys are actually required. If you make them all required in your type definition, you'll end up fighting the compiler every time you try to use the library with a simple configuration. Keep it flexible, but accurate.
One last tip: place these files in a folder like @types at the root of your project and make sure your tsconfig.json is configured to look there via the typeRoots option. This keeps your custom definitions separate from your actual application logic, making the project much cleaner for the next developer who comes along.
📋 Practical Task
Creating Type Definitions for the "Easy-Auth-JS" Library
You are using a small, untyped legacy library called easy-auth-js. The library has the following JavaScript implementation (which you cannot change):
// easy-auth-js.js
export function login(username, password) { /* ... */ }
export function logout() { /* ... */ }
export function setUserSession(sessionData) { /* ... */ }
The sessionData object is expected to have a token (string) and an optional expiry (number).
Your Task: Create a declaration file easy-auth-js.d.ts that provides full type safety for this library. You must:
- Use
declare module 'easy-auth-js'. - Define the
loginandlogoutfunctions with appropriate types. - Create an interface for
SessionDataand use it as the parameter type forsetUserSession.
There are no comments for now.