🔷 TypeScript Q22 / 101

What is declaration merging?

AI-Powered Answer ✓ Answered

Declaration merging is a unique feature in TypeScript where the compiler merges two or more separate declarations with the same name into a single definition. This allows for augmenting existing types, namespaces, or modules, providing a flexible way to extend and combine type definitions.

What is Declaration Merging?

In TypeScript, when you have multiple declarations for the same entity (e.g., an interface, a namespace, a class with a namespace, or a function with a namespace), the compiler automatically merges them into a single, unified declaration. This mechanism is crucial for extensibility and for providing ambient declarations that augment existing JavaScript libraries.

How Declaration Merging Works

1. Interface Merging

The simplest and most common form of declaration merging is with interfaces. If two interfaces with the same name are declared, their members are merged into a single interface. If members have the same name, they must be of compatible types. If methods have the same name, their signatures are merged into an overload list, with later declarations taking precedence in the order of resolution.

typescript
interface MyInterface {
  id: number;
}

interface MyInterface {
  name: string;
  greet(): void;
}

// The merged MyInterface will be:
// interface MyInterface {
//   id: number;
//   name: string;
//   greet(): void;
// }

2. Namespace Merging

Namespaces with the same name are also merged. The exports from each namespace declaration become part of a single, merged namespace. This allows you to split the definition of a namespace across multiple files or declarations.

typescript
namespace MyNamespace {
  export const x = 10;
}

namespace MyNamespace {
  export function func() { console.log(MyNamespace.x); }
}

// The merged MyNamespace will be:
// namespace MyNamespace {
//   export const x = 10;
//   export function func() { console.log(MyNamespace.x); }
// }

3. Namespace with Class/Function/Enum Merging

This pattern allows a namespace to add static members to an existing class, function, or enum. The namespace must be declared immediately after the entity it augments. The merged result gives the class/function/enum additional static properties or methods that are defined within the namespace.

typescript
class MyClass {
  constructor(public value: number) {}
}

namespace MyClass {
  export function createInstance(value: number) {
    return new MyClass(value);
  }
  export const staticProp = 'hello';
}

const instance = MyClass.createInstance(5);
console.log(MyClass.staticProp); // Output: hello

Key Rules and Considerations

  • Order of declaration generally doesn't matter for property merging, but for method overloads, later declarations have higher precedence (they appear later in the overload list).
  • Only certain declaration types can be merged: interfaces, namespaces, enums, classes (with namespaces), and functions (with namespaces). Type aliases cannot be merged.
  • When merging interfaces, if two properties with the same name have different, incompatible types, a compile-time error will occur.
  • For namespaces merging with classes/functions/enums, the namespace must immediately follow the entity it's augmenting in the same source file.

Common Use Cases

  • Augmenting Global Objects: Extending built-in types like Window, HTMLElement, or NodeJS.ProcessEnv by adding custom properties (e.g., declare global { interface Window { myCustomProp: string; } }).
  • Extending External Modules: Adding types or functionality to third-party library modules, often found in *.d.ts files (e.g., declare module 'express' { interface Request { user?: User; } }).
  • Splitting Large Type Definitions: Breaking down complex interface or namespace definitions across multiple files for better organization and maintainability.

Declaration merging is a powerful and essential feature in TypeScript for managing and extending type definitions, particularly when working with existing JavaScript libraries or large codebases where types need to be incrementally built up or augmented.