Declaration Files

Difficulty: Advanced

Declaration files (.d.ts) are TypeScript files that contain only type information and no runtime code. They serve as a bridge between TypeScript's type system and JavaScript libraries that were not written in TypeScript. When TypeScript encounters a .d.ts file, it uses the type information for type checking but does not emit any JavaScript from it. Declaration files are the backbone of TypeScript's interoperability with the vast JavaScript ecosystem.

The `declare` keyword is used to tell TypeScript about the shape of code that exists elsewhere - in a JavaScript library, a global variable injected by a script tag, or a runtime environment API. When you write `declare function jQuery(selector: string): any`, you are not creating a function; you are telling TypeScript that this function exists at runtime and describing its type signature. The `declare` keyword can be applied to variables, functions, classes, enums, namespaces, and modules.

Ambient module declarations allow you to type non-TypeScript modules such as CSS files, JSON files, images, or any other asset that your bundler can import. By writing `declare module '*.css'`, you tell TypeScript that any CSS import will resolve to a specific type. This is commonly used in projects with Webpack or Vite loaders that allow importing non-JavaScript assets.

To create declaration files for your own code, you can either write them manually or let the TypeScript compiler generate them using the `declaration: true` option in tsconfig.json. When publishing a library, you should include .d.ts files alongside your compiled JavaScript so that TypeScript consumers get full type checking. The `types` or `typings` field in package.json points to your entry declaration file.

Code examples

Using the declare Keyword

// globals.d.ts - declaring global variables and functions
// declare const API_URL: string;
// declare function fetchData(endpoint: string): Promise<any>;
// declare class EventBus {
//   on(event: string, callback: Function): void;
//   emit(event: string, data?: any): void;
// }

// Simulating declared globals
const API_URL: string = 'https://api.example.com';

function fetchData(endpoint: string): Promise<any> {
  return Promise.resolve({ url: `${API_URL}${endpoint}` });
}

class EventBus {
  on(event: string, callback: Function): void {
    console.log(`Listener added for: ${event}`);
  }
  emit(event: string, data?: any): void {
    console.log(`Event emitted: ${event}`);
  }
}

console.log(API_URL);
const bus = new EventBus();
bus.on('click', () => {});
bus.emit('click');

The declare keyword tells TypeScript about values that exist at runtime but are defined elsewhere. In a .d.ts file, all declarations are implicitly ambient.

Ambient Module Declarations

// In a .d.ts file, you would write:
// declare module '*.css' {
//   const classes: Record<string, string>;
//   export default classes;
// }
// declare module '*.svg' {
//   const src: string;
//   export default src;
// }
// declare module '*.png' {
//   const src: string;
//   export default src;
// }

// Simulating what these declarations enable:
interface CSSModule {
  [className: string]: string;
}

const styles: CSSModule = {
  container: 'container_x7k2',
  header: 'header_a3b1',
  button: 'button_m9p4'
};

console.log(styles.container);
console.log(styles.header);
console.log(Object.keys(styles).length + ' CSS classes');

Ambient module declarations tell TypeScript what type to assign to imports that match a given pattern. This is essential for bundlers that support importing non-JS assets.

Generating Declaration Files

// When you compile with { declaration: true }, TypeScript generates .d.ts files
// Source: math-lib.ts
// export function add(a: number, b: number): number { return a + b; }
// export interface MathResult { value: number; operation: string; }

// Generated: math-lib.d.ts
// export declare function add(a: number, b: number): number;
// export interface MathResult { value: number; operation: string; }

// The .d.ts file strips implementation, keeping only type signatures

function add(a: number, b: number): number { return a + b; }

interface MathResult {
  value: number;
  operation: string;
}

const result: MathResult = { value: add(3, 4), operation: 'addition' };
console.log(result.value);
console.log(result.operation);

The TypeScript compiler can auto-generate .d.ts files that strip out implementations, leaving only type signatures for consumers of your library.

Key points

Concepts covered

declaration files, .d.ts, declare keyword, ambient modules, ambient declarations, global augmentation