How to consume a d.ts file's exported const types if the library's implementation is not integrated with the TS project? How to consume a d.ts file's exported const types if the library's implementation is not integrated with the TS project? typescript typescript

How to consume a d.ts file's exported const types if the library's implementation is not integrated with the TS project?


If the module has an export as namespace myLib then module already exports the library as a global object. So you can just use the library as:

let a:myLib.AnInterface;let b =  myLib.doThing1();

This is true as long as the file you are using the library in is not a module itself (ie it contains no import and no export statements).

export {} // module nowlet a:myLib.AnInterface; // Types are still ok without the importlet b =  myLib.doThing1(); // Expressions are not ok, ERR: 'myLib' refers to a UMD global, but the current file is a module. Consider adding an import instead.ts(2686)

You can add a property to Window that is the same type as the type of the library using an import type (added in 2.9 in believe)

// myLibGlobal.d.ts// must not be a module, must not contain import/ export interface Window {    myLib: typeof import('./myLib') // lib name here}//usage.tsexport {} // modulelet a:myLib.AnInterface; // Types are still ok without the import (if we have the export as namespacelet b =  window.myLib.doThing1(); // acces through window ok now

Edit

Apparently the Typescript team has actually been working on something for this very issue. As you can read in this PR the next version of typescript will include a allowUmdGlobalAccess flag. This flag will allow access to UMD module globals from modules. With this flag set to true, this code will be valid:

export {} // module nowlet a:myLib.AnInterface; // Types are still ok without the importlet b =  myLib.doThing1(); // ok, on typescript@3.5.0-dev.20190425

This mean you can just access the module exports without the need for using window. This will work if the global export is compatible with the browser which I would expect it to be.


What are you dealing with

when a library assigns itself to a property of the window

That's called a UMD package. These are the ones consumed by adding a link inside a <script /> tag in your document, and they attach themselves to the global scope.

UMD packages don't have to be consumed this way — they can also be consumed as modules, using an import (or require) statement.

TypeScript supports both usages.

How should UMD packages be typed

declare namespace Foo {  export const bar: string;  export type MeaningOfLife = number;}export as namespace Foo;export = Foo;

This definition tells TypeScript that:

  • if the consuming file is a script, then there's a variable called bar in the global scope
  • if the consuming file is a module, then you can import bar using named imports or import the entire namespace using the wildcard (*) import.

What's the difference between a script and a module?

A script would be a piece of JavaScript running inside a <script /> tag in your HTML document. It can be inlined or loaded from a file. It's how JavaScript has always been used in the browser.

A module is a JavaScript (TypeScript) file that has at least one import or export statement. They are part of the ECMAScript standard and are not supported everywhere. Usually, you create modules in your project and let a bundler like Webpack create a bundle for your application to use.

Consuming UMD packages

A script, variables, and types are used by accessing the global Foo namespace (just like jQuery and $):

const bar = Foo.bar;const meaningOfLife: Foo.MeaningOfLife = 42;

A script, the type of bar is imported using the import type syntax:

const baz: typeof import ('foo').bar = 'hello';

A module, the bar variable is imported using a named import.

import { bar } from 'foo';bar.toUpperCase();

A module, the entire package is imported as a namespace:

import * as foo from 'foo';foo.bar.toUpperCase();

Global scope vs. window

If such a library is typed correctly, you as the consumer don't have to do anything to make it work. It will be available in your global scope automatically and no augmentation for Window will be necessary.

However, if you'd like to attach the contents of your library to window explicitly, you can also do that:

declare global {  interface Window {    Foo: typeof import('foo');  }}window.Foo.bar;