TypeScript — Module Augmentation Overwrites Declarations Instead of Merging Them

TypeScript comes with a feature called declaration merging. Declaration merging means that the TypeScript compiler merges two or more separate declarations that use the same name into a single definition. Merging definitions is helpful if you’re extending a contract with custom functionality and want to tell TypeScript about the newly added properties or methods.

Declaration merging could cause issues when used in combination with module augmentation of third-party packages. This tutorial shows you how to extend interfaces in modules instead of overwriting them.

TypeScript Series Overview

The Problem: Module Augmentation Overwrote Type Declarations

The Supercharge Node.js framework provides interfaces in a @supercharge/contracts package. We wanted to extend the HTTP header contract to provide types for custom headers that we use in a project.

We added a types directory in our project and created an http.ts file that contains all custom type declarations:

types  
└ http.ts

The type declarations within the types/http.ts file augment the @supercharge/contracts package:

declare module '@supercharge/contracts' {  
  export interface HttpRequestHeaders {
    // add custom HTTP request headers

    'X-Rate-Limit-Limit': number | undefined
    'X-Rate-Limit-Reset': number | undefined
    'X-Rate-Limit-Remaining': number | undefined
  }
}

What happened when adding our custom type declarations: Visual Studio code lost all exports from @supercharge/contracts. VS Code didn’t find any exported type from the package except our HttpRequestHeaders interface. What a bummer! Let’s fix that.

Using Declaration Merging with Module Augmentation

We found a related issue within the TypeScript repository on GitHub. We lost all exported declarations from @supercharge/contracts because we used a regular TypeScript file with .ts file extension that didn’t have an export. No export from a TypeScript file means it’s not a module. Make it a module by having an import or an export within the file.

In our case, we added the import '@supercharge/contracts' to the beginning of the file because we don’t have anything to export:

import '@supercharge/contracts'

declare module '@supercharge/contracts' {  
  export interface HttpRequestHeaders {
    // add custom HTTP request headers

    'X-Rate-Limit-Limit': number | undefined
    'X-Rate-Limit-Reset': number | undefined
    'X-Rate-Limit-Remaining': number | undefined
  }
}

Importing the @supercharge/contracts package fixed the issue and TypeScript’s compiler picked up all types, also the merged ones. Awesome!

Enjoy TypeScript declaration merging in combination with module augmentation!


Mentioned Resources

Explore the Library

Find interesting tutorials and solutions for your problems.