lib.d.ts

lib.d.ts

A special declaration file lib.d.ts ships with every installation of TypeScript. This file contains the ambient declarations for various common JavaScript constructs present in JavaScript runtimes and the DOM.

  • This file is automatically included in the compilation context of a TypeScript project.

  • The objective of this file is to make it easy for you to start writing type checked JavaScript code.

You can exclude this file from the compilation context by specifying the --noLib compiler command line flag (or "noLib" : true in tsconfig.json).

Example Usage

As always let's look at examples of this file being used in action:

var foo = 123;
var bar = foo.toString();

This code type checks fine because the toString function is defined in lib.d.ts for all JavaScript objects.

If you use the same sample code with the noLib option you get a type check error:

So now that you understand the importance of lib.d.ts, what do its contents look like? We examine that next.

lib.d.ts Inside Look

The contents of lib.d.ts are primarily a bunch of variable declarations e.g. window, document, math and a bunch of similar interface declarations e.g. Window , Document, Math.

The simplest way to read the documentation and type annotations of global stuff is to type in code that you know works e.g. Math.floor and then F12 (go to definition) using your IDE (VSCode has great support for this).

Let's look at a sample variable declaration, e.g. window is defined as:

That is just a simple declare var followed by the variable name (here window) and an interface for a type annotation (here the Window interface). These variables generally point to some global interface e.g. here is a small sample of the (actually quite massive) Window interface:

You can see that there is a lot of type information in these interfaces. In the absence of TypeScript you would need to keep this in your head. Now you can offload that knowledge on the compiler with easy access to it using things like intellisense.

There is a good reason for using interfaces for these globals. It allows you to add additional properties to these globals without a need to change lib.d.ts. We will cover this concept next.

Modifying Native Types

Since an interface in TypeScript is open ended this means that you can just add members to the interfaces declared in lib.d.ts and TypeScript will pick up on the additions. Note that you need to make these changes in a global module for these interfaces to be associated with lib.d.ts. We even recommend creating a special file called global.d.ts for this purpose.

Here are a few example cases where we add stuff to window, Math, Date:

Example window

Just add stuff to the Window interface e.g.:

This will allow you to use it in a type safe manner:

Example Math

The global variable Math is defined in lib.d.ts as (again, use your dev tools to navigate to definition):

i.e. the variable Math is an instance of the Math interface. The Math interface is defined as:

This means that if you want to add stuff to the Math global variable you just need to add it to the Math global interface, e.g. consider the seedrandom project which adds a seedrandom function to the global Math object. This can be declared quite easily:

And then you can just use it:

Example Date

If you look at the definition of the Date variable in lib.d.ts you will find:

The interface DateConstructor is similar to what you have seen before with Math and Window in that it contains members you can use off of the Date global variable e.g. Date.now(). In addition to these members it contains construct signatures which allow you to create Date instances (e.g. new Date()). A snippet of the DateConstructor interface is shown below:

Consider the project datejs. DateJS adds members to both the Date global variable and Date instances. Therefore a TypeScript definition for this library would look like (BTW the community has already written this for you in this case):

This allows you to do stuff like the following in a TypeSafe manner:

Example string

If you look inside lib.d.ts for string you will find stuff similar to what we saw for Date (String global variable, StringConstructor interface, String interface). One thing of note though is that the String interface also impacts string literals as demonstrated in the below code sample:

Similar variables and interfaces exist for other things that have both static and instance members like Number, Boolean, RegExp, etc. and these interfaces affect literal instances of these types as well.

Example string redux

We recommended creating a global.d.ts for maintainability reasons. However, you can break into the global namespace from within a file module if you desire so. This is done using declare global { /*global namespace here*/ }. E.g. the previous example can also be done as:

Using your own custom lib.d.ts

As we mentioned earlier, using the --noLib boolean compiler flag causes TypeScript to exclude the automatic inclusion of lib.d.ts. There are various reasons why this is a useful feature. Here are a few of the common ones:

  • You are running in a custom JavaScript environment that differs significantly from the standard browser based runtime environment.

  • You like to have strict control over the globals available in your code. E.g. lib.d.ts defines item as a global variable and you don't want this to leak into your code.

Once you have excluded the default lib.d.ts you can include a similarly named file into your compilation context and TypeScript will pick it up for type checking.

Note: be careful with --noLib. Once you are in noLib land, if you choose to share your project with others, they will be forced into noLib land (or rather your lib land). Even worse, if you bring their code into your project you might need to port it to your lib based code.

Compiler target effect on lib.d.ts

Setting the compiler target to es6 causes the lib.d.ts to include additional ambient declarations for more modern (es6) stuff like Promise. This magical effect of the compiler target changing the ambience of the code is desirable for some people and for others it's problematic as it conflates code generation with code ambience.

However, if you want finer grained control of your environment, you should use the --lib option which we discuss next.

lib option

Sometimes (many times) you want to decouple the relationship between the compile target (the generated JavaScript version) and the ambient library support. A common example is Promise, e.g. today (in June 2016) you most likely want to --target es5 but still use the latest features like Promise. To support this you can take explicit control of lib using the lib compiler option.

Note: using --lib decouples any lib magic from --target giving you better control.

You can provide this option on the command line or in tsconfig.json (recommended):

Command line:

tsconfig.json:

The libs can be categorized as follows:

  • JavaScript Bulk Feature:

    • es5

    • es6

    • es2015

    • es7

    • es2016

    • es2017

    • esnext

  • Runtime Environment

    • dom

    • dom.iterable

    • webworker

    • scripthost

  • ESNext By-Feature Options (even smaller than bulk feature)

    • es2015.core

    • es2015.collection

    • es2015.generator

    • es2015.iterable

    • es2015.promise

    • es2015.proxy

    • es2015.reflect

    • es2015.symbol

    • es2015.symbol.wellknown

    • es2016.array.include

    • es2017.object

    • es2017.sharedmemory

    • esnext.asynciterable

NOTE: the --lib option provides extremely fine tuned control. So you most likely want to pick an item from the bulk + environment categories. If --lib is not specified a default library is injected:

  • For --target es5 => es5, dom, scripthost

  • For --target es6 => es6, dom, dom.iterable, scripthost

My Personal Recommendation:

Example Including Symbol with ES5:

Symbol API is not included when target is es5. In fact, we receive an error like: [ts] Cannot find name 'Symbol'. We can use "target": "es5" in combination with "lib" to provide Symbol API in TypeScript:

Polyfill for old JavaScript engines

Egghead PRO Video on this subject

There are quite a few runtime features that are like Map / Set and even Promise (this list will of course change over time) that you can use with modern lib options. To use these all you need to do is use core-js. Simply install:

And add an import to your application entry point:

And it should polyfill these runtime features for you 🌹.

Last updated