web-dev-qa-db-fra.com

Comment consommer les types const exportés d'un fichier d.ts si l'implémentation de la bibliothèque n'est pas intégrée au projet TS?

DefinitelyTyped a des définitions de type pour de nombreuses bibliothèques, mais assez souvent je ne peux pas trouver un bon moyen de les utiliser lorsque l'implémentation Javascript est séparée du TypeScript, comme lorsqu'une bibliothèque s'assigne à une propriété de la fenêtre via un

<script src="https://example.com/library.js">

et lorsque le bundle JS que je gère se trouve dans un autre script distinct. (Même si tout est regroupé y compris la bibliothèque est la méthode standard et fiable, supposez pour la question que je n'ai pas la possibilité d'importer la bibliothèque dans mon projet TS proprement dit.) Par exemple , disons que je trouve un joli fichier de définition pour une bibliothèque nommée myLib:

// my-lib.d.ts
export const doThing1: () => number;
export const doThing2: () => string;
export const version: string;
export interface AnInterface {
  foo: string;
}
export as namespace myLib;

Dans JS, je peux utiliser myLib en appelant window.myLib.doThing1() et window.myLib.doThing2(). Comment puis-je importer la forme de l'ensemble de l'objet window.myLib Afin de pouvoir le déclarer en tant que propriété de window? Je peux voir que je peux importer les exportés interfaces, par exemple:

// index.ts
import { AnInterface } from './my-lib';
const something: AnInterface = { foo: 'foo' };
console.log(something.foo);

Cela fonctionne, mais je veux accéder à la forme de l'objet de bibliothèque réel et à ses valeurs de propriété (les fonctions et les chaînes et autres), pas seulement aux interfaces. Si je fais

import * as myLib from './my-lib';

alors l'identifiant myLib devient un espace de noms, à partir duquel je peux référencer les interfaces exportées, mais comme ci-dessus, je n'ai toujours pas accès aux formes export const et export function de my-lib.d.ts. (Et, bien sûr, essayer d'utiliser l'espace de noms importé pour déclarer l'objet bibliothèque ne fonctionne pas: Cannot use namespace 'myLib' as a type. Même si je pouvais le faire, ce ne serait pas nécessairement sûr, car la bibliothèque emballée pour le navigateur peut être structuré légèrement différemment de l'objet d'exportation Node de la bibliothèque)

Si je copie et colle manuellement des parties du d.ts Dans mon propre script, je peux pirater ensemble quelque chose qui fonctionne:

// index.ts
declare global {
  interface Window {
    myLib: {
      doThing1: () => number;
      doThing2: () => string;
      version: string;
    };
  }
}

Mais c'est désordonné, long, et ce n'est sûrement pas la façon appropriée de faire quelque chose comme ça. Quand je rencontre ce genre de situation, je voudrais comme pouvoir faire quelque chose de court et d'élégant comme:

// index.ts
import myLibObjectInterface from './my-lib.d.ts'; // this line is not correct
declare global {
  interface Window {
    myLib: myLibObjectInterface
  }
}

Certains fichiers de définition incluent une interface pour l'objet bibliothèque, comme jQuery, qui:

// index.d.ts
/// <reference path="JQuery.d.ts" />

// jQuery.d.ts
interface JQuery<TElement = HTMLElement> extends Iterable<TElement> {
  // lots and lots of definitions

Alors tout va bien - je peux simplement utiliser interface Window { $: jQuery }, Mais de nombreuses bibliothèques non créées à l'origine pour la consommation du navigateur n'offrent pas une telle interface.

Comme mentionné précédemment, la meilleure solution serait que l'implémentation de la bibliothèque soit intégrée au projet TS, permettant à la bibliothèque et à ses types d'être imported et utilisés sans problème, mais si ce n'est pas possible, dois-je il vous reste bon options? Je pourrais examiner les propriétés du véritable objet de bibliothèque et ajouter une interface au fichier de définition qui inclut toutes ces propriétés et leurs types, mais avoir à modifier le ou les fichiers de définition de source semi-canoniques acceptés par DT et utilisés par tout le monde se sent faux. Je préférerais pouvoir importer les formes des exportations du fichier de définition et créer une interface à partir de celles-ci sans modifier le fichier d'origine, mais cela peut ne pas être possible.

Existe-t-il une solution plus élégante, ou les fichiers de définition que j'ai rencontrés sont-ils simplement insuffisamment adaptés à mon objectif, et doivent donc être modifiés?

16
Snow

Si le module a un export as namespace myLib alors le module exporte déjà la bibliothèque en tant qu'objet global. Vous pouvez donc simplement utiliser la bibliothèque comme:

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

Cela est vrai tant que le fichier dans lequel vous utilisez la bibliothèque n'est pas un module lui-même (c'est-à-dire qu'il ne contient pas d'instructions import et export).

export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let 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)

Vous pouvez ajouter à Window une propriété qui est du même type que le type de la bibliothèque en utilisant un type d'importation (ajouté dans 2.9 en croire)

// myLibGlobal.d.ts
// must not be a module, must not contain import/ export 
interface Window {
    myLib: typeof import('./myLib') // lib name here
}


//usage.ts
export {} // module
let a:myLib.AnInterface; // Types are still ok without the import (if we have the export as namespace
let b =  window.myLib.doThing1(); // acces through window ok now

Modifier

Apparemment, l'équipe TypeScript a en fait travaillé sur quelque chose pour ce problème. Comme vous pouvez le lire dans ce PR la prochaine version de TypeScript inclura un indicateur allowUmdGlobalAccess. Cet indicateur permettra d'accéder aux globaux des modules UMD à partir des modules. Avec cet indicateur défini sur true, ce code sera valide:

export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let b =  myLib.doThing1(); // ok, on [email protected]

Cela signifie que vous pouvez simplement accéder aux exportations du module sans avoir besoin d'utiliser la fenêtre. Cela fonctionnera si l'exportation globale est compatible avec le navigateur que je m'attendrais à ce qu'il soit.

De quoi avez-vous affaire

lorsqu'une bibliothèque s'assigne à une propriété de la fenêtre

C'est ce qu'on appelle un package UMD . Ce sont ceux consommés en ajoutant un lien dans un <script /> tag dans votre document, et ils s'attachent à la portée globale.

Les packages UMD ne doivent pas être consommés de cette façon - ils peuvent également être consommés en tant que modules, à l'aide d'une instruction import (ou require).

TypeScript prend en charge les deux utilisations.

Comment faut-il taper les paquets UMD

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

export as namespace Foo;
export = Foo;

Cette définition indique à TypeScript que:

  • si le fichier consommateur est un script , alors il y a une variable appelée bar dans la portée globale
  • si le fichier consommateur est un module , vous pouvez alors importer bar à l'aide d'importations nommées ou importer l'intégralité de l'espace de noms à l'aide du caractère générique (*) importer.

Quelle est la différence entre un script et un module ?

Un script serait un morceau de JavaScript exécuté dans un <script /> tag dans votre document HTML. Il peut être inséré ou chargé à partir d'un fichier. C'est ainsi que JavaScript a toujours été utilisé dans le navigateur.

Un module est un fichier JavaScript (TypeScript) qui contient au moins une instruction import ou export. Ils font partie de la norme ECMAScript et ne sont pas pris en charge partout. Habituellement, vous créez des modules dans votre projet et laissez un bundler comme Webpack créer un bundle pour votre application à utiliser.

Consommation de packages UMD

Un script, des variables et des types sont utilisés en accédant à l'espace de noms global Foo (tout comme jQuery et $):

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

Un script, le type de bar est importé à l'aide de la syntaxe du type d'importation :

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

Un module, la variable bar est importé à l'aide d'une importation nommée.

import { bar } from 'foo';

bar.toUpperCase();

Un module, l'ensemble du package est importé en tant qu'espace de noms:

import * as foo from 'foo';

foo.bar.toUpperCase();

Portée globale vs fenêtre

Si une telle bibliothèque est correctement saisie, vous, en tant que consommateur, n'avez rien à faire pour la faire fonctionner. Il sera disponible dans votre portée globale automatiquement et aucune augmentation de Window ne sera nécessaire.

Cependant, si vous souhaitez attacher explicitement le contenu de votre bibliothèque à window, vous pouvez également le faire:

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

window.Foo.bar;
3
Karol Majewski