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
- How to Export Transpiled Code From package.json
- Use Nodemon to Restart Your Server on Changes
- How to Allow Trailing Commas (Comma-Dangle) With Typescript-ESLint
- Use SWC to Speed up TypeScript Code
- Fixing Null Type Ignored in Union Type
- How to Remove Index Signature from a Type
- Module Augmentation Overwrites Declarations Instead of Merging Them
- Get All Keys of an Enum
- Get All Values of an Enum
- Using a String as Enum Key
- Understanding “keyof typeof”
- Get Type of Class Properties and Omit Methods
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!