TIL: Explicit TypeScript lib References
October 08, 2020 • 4 minute read
For example, if the package uses an
ES2018 feature — like async iterables — but the application’s TypeScript configuration is
ES2015, the behaviour can be surprising and difficult to understand.
This has happened with RxJS. In version 7, an async iterable can be used as an observable input — it can be passed to an observable creator, like
from — so the
ObservableInput type looks like this:
export type ObservableInput<T> = | SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T> | AsyncIterableIterator<T>;
If the TypeScript configuration for the application that consumes RxJS does not have
lib configured as
ES2018 or later, the
AsyncIterableIterator type won’t be referenced and the type inference will behave in an unexpected manner.
Let’s take a look at this snippet:
const answers = from([42, 54]);
The observable input is an array of numbers, so the inferred type of
answers should be
Observable<number>, but it’s not. If
lib is configured as
ES2017 or earlier, the inferred type is
And it’s not at all clear that the unexpected inference is related to the TypeScript configuration — in particular, to the
lib that’s specified. 😬
How does the configuration work?
There are two ways the TypeScript
lib compiler option can be configured.
liboption is specified. In which case, TypeScript uses a the
targetand adds references to the
For example, if the
liboption will be
["ES5", "DOM", "ScriptHost"].
liboption can be specified explicitly. In which case, the
targetoption has no bearing on which libraries are referenced.
It’s common for the
lib option to be specified because:
- some developers (like me, before I wrote this post 😅) don’t read all of the TypeScript documentation and don’t understand how the
liboptions interact; and
- Node developers don’t want the
DOMtypes to be referenced.
That means it’s quite likely that there will be some applications that won’t have TypeScript configured for modern features used within packages upon which they depend.
The problem can be avoided if the package is able to have some say in the TypeScript libraries that are referenced. Ryan Cavanaugh pointed out that this can be done by adding a TypeScript triple-slash directive to specify a
lib reference, like this:
/// <reference lib="ESNext.AsyncIterable" />
The directive can be added to a
.ts source file. When TypeScript compiles the source file, it will include the directive in the generated
.d.ts file. Then, when the consuming application imports the
.d.ts file, the directive will be processed and the specified library will be referenced.
The solution works. It resolves the problem mentioned above — with the directive, the type is correctly inferred to be
Observable<number> — but it’s not perfect. Ryan mentions that referencing
ESNext.AsyncIterable also references a handful of other types — some of which might not have been otherwise referenced by the application developer’s TypeScript configuration.
The solution is, however, much better than the alternative: countless developer hours wasted and an interminable number of issues opened. I mean, we will, of course, mention RxJS’s
lib requirement — a minimum of
ES2018 — in the documentation, but who’s going to read that? Probably not me. 🙂