Declaration Files (.d.ts) & Ambient Modules
Quick Overview
A `.d.ts` file describes types with no implementation, which is how JavaScript packages get typed without being rewritten. `@types/*` packages are exactly that, published separately from the library.
Deep Dive
1. Typing JavaScript Libraries
Many third-party npm packages are written in raw JavaScript without TypeScript source code. Declaration Files (.d.ts) provide type annotations for JavaScript code without altering the underlying JavaScript execution.
2. What is a .d.ts File?
A .d.ts file contains ONLY type declarations (no executable code). The TypeScript compiler uses it to provide autocompletion and type checking for untyped JavaScript libraries.
3. Ambient Module Declarations (declare module)
If an npm package lacks types, you can declare ambient module shapes in a local types.d.ts file:
// types/legacy-lib.d.ts
declare module "legacy-chart-library" {
export function renderChart(elementId: string, data: number[]): void;
export const version: string;
}
Now you can import and use the untyped library safely in your project:
import { renderChart } from "legacy-chart-library";
renderChart("my-canvas", [10, 20, 30]); // Fully typed!
4. Global Type Augmentation
Augment ambient global objects (like adding custom properties to window or Express Request):
declare global {
interface Window {
analyticsToken: string;
}
}
// Now valid throughout your project!
window.analyticsToken = "token_123";
5. Common Mistakes
- Putting Implementation Code in .d.ts Files: Declaration files can ONLY contain type declarations, interfaces, and
declarestatements. Including executable JavaScript code throws compile errors!
6. Next Steps
Use DefinitelyTyped (npm i -D @types/node) for open-source library types. Next, let's explore the satisfies operator!
Next Up in TypeScript & Type System Mechanics
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
