Difficulty: Advanced
What are declaration files (.d.ts) in TypeScript? How do they enable TypeScript to work with JavaScript libraries?
Declaration files (.d.ts) contain only type information with no runtime code. They describe the shape of JavaScript libraries so TypeScript can type-check code that uses them.
Sources of declarations: 1. Generated by tsc (declaration: true in tsconfig) - published alongside compiled JS 2. DefinitelyTyped (@types/xxx packages) - community-maintained types for JS libraries 3. Hand-written ambient declarations for custom or untyped code
Declaration files use 'declare' keyword for ambient declarations - telling TypeScript that something exists at runtime without providing implementation. They are essential for the TypeScript ecosystem because they allow gradual adoption without rewriting libraries.
// math-lib.d.ts - types for a JavaScript library
declare module 'math-lib' {
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
export function multiply(a: number, b: number): number;
export interface MathOptions {
precision: number;
rounding: 'ceil' | 'floor' | 'round';
}
export class Calculator {
constructor(options?: Partial<MathOptions>);
compute(expression: string): number;
reset(): void;
}
export default Calculator;
}
// Now TypeScript understands imports from 'math-lib'
import Calculator, { add, MathOptions } from 'math-lib';
const result = add(1, 2); // number
const calc = new Calculator(); // Calculator
calc.compute('2 + 2'); // number
declare module tells TypeScript what types a module exports. This lets you use JavaScript libraries with full type safety and autocompletion.
// globals.d.ts - global type declarations
// Declare global variables (e.g., injected by script tags)
declare const API_BASE_URL: string;
declare const APP_VERSION: string;
declare function sendAnalytics(event: string, data?: object): void;
// Extend existing global interfaces
declare global {
interface Window {
dataLayer: Record<string, unknown>[];
__APP_CONFIG__: {
apiUrl: string;
environment: 'dev' | 'staging' | 'production';
};
}
// Add custom properties to process.env
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
}
}
}
export {}; // Makes this a module (required for declare global)
// Usage is now type-safe
window.__APP_CONFIG__.apiUrl; // string
process.env.DATABASE_URL; // string
process.env.RANDOM_VAR; // Error if not declared
declare global extends existing global types. This is commonly used to type environment variables and window properties injected by build tools.
// Installing types for a JS library
// npm install --save-dev @types/lodash
// npm install --save-dev @types/express
// npm install --save-dev @types/node
// TypeScript auto-discovers @types packages in node_modules
import _ from 'lodash';
_.chunk([1, 2, 3, 4], 2); // number[][] - fully typed!
// For libraries without @types, create a shim:
// untyped-lib.d.ts
declare module 'untyped-lib' {
const lib: any;
export default lib;
}
// Minimal: allows import without errors, but no type safety
// Better: add specific types incrementally
declare module 'untyped-lib' {
export function doSomething(input: string): Promise<void>;
export function getData(): Record<string, unknown>;
}
// tsconfig.json settings for declarations
// {
// "compilerOptions": {
// "declaration": true, // Generate .d.ts files
// "declarationDir": "./types", // Output directory
// "typeRoots": ["./types", "./node_modules/@types"]
// }
// }
DefinitelyTyped (@types/*) is the largest collection of community-maintained type declarations. For untyped libraries, write a minimal .d.ts shim and improve it incrementally.
Declaration Files, .d.ts, DefinitelyTyped, @types, Ambient Declarations, Triple-Slash Directives