For one of my recent projects I wanted to introduce some flexibility by offering extensibility. Offering extensibility in TypeScript can be done in multiple ways, most notably we have:
- Generics (powerful, transparent, explicit)
- Declaration merging (more restrictive, hidden, implicit)
I first went for generics, but then realized that offering more and more extension points will result in a total bloat of type arguments. At the end I introduced declaration merging as an easy way out.
The theory is this: We introduce a type (interface) such as Foo
which extends another (empty) interface called CustomFoo
. This interface sits at a "known" location and can then be modified by plugins:
declare module 'base-lib/known-location' {
interface CustomFoo {
// put new stuff in here
}
}
Great! Now that works flawlessly and from any number of plugins / sources.
There is just one drawback / question that comes up. My project required unions on some positions. Mostly, these unions are used as discriminated unions - but that does not matter much. Let's pretend I have the following defined:
export interface HtmlComponent {
type: 'html';
render: RenderType;
}
export interface ReactComponent {
type: 'react';
render: RenderType;
}
export type AnyComponent = HtmlComponent | ReactComponent;
How can I enable plugins to extend AnyComponent
, e.g., one plugin to bring in a VueComponent
? How can the union be extended?
Let's start with what we know. We know that interface can easily be extended.
export interface Components extends CustomComponents {
html: HtmlComponent;
react: ReactComponent;
}
The CustomComponent
interface would be empty and placed on a "known" location - like beforehand. So far so good. But how can we combine the properties of the Components
interface to form a union?
The answer to this lies in the index operator - which can be used in types dynamically.
export type UnionOf = { [K in keyof T]: T[K] }[keyof T];
export type AnyComponent = UnionOf;
Together with type guards and conditional types this forms a deadly combination that gives us great possibilities!