Modules

Références

L'actualité

Librairie

L'information

Introduction


Remarque à propos de la terminologie: Il est important de noter que dans TypeScript 1.5, la nomenclature a été modifiée. Les "modules internes" sont maintenant des "espaces de noms". Les "modules externes" sont désormais simplement des "modules", afin de s'aligner sur la terminologie d'ECMAScript 2015 (c'est-à-dire qu'elle module X { équivaut à celle que nous préférons maintenant namespace X { ).

À partir de ECMAScript 2015, JavaScript a un concept de modules. TypeScript partage ce concept.

Les modules sont exécutés dans leur propre domaine, pas dans le domaine global; cela signifie que les variables, fonctions, classes, etc... déclarées dans un module ne sont pas visibles en dehors du module, sauf si elles sont explicitement exportées à l'aide de l'un des formulaires export. Inversement, pour consommer une variable, une fonction, une classe, une interface, etc... exportée depuis un module différent, elle doit être importée à l'aide de l'un des formulaires import.

Les modules sont déclaratifs; les relations entre les modules sont spécifiées en termes d'importations et d'exportations au niveau du fichier.

Les modules s'importent les uns les autres à l'aide d'un chargeur de module. Au moment de l'exécution, le chargeur de modules est responsable de la localisation et de l'exécution de toutes les dépendances d'un module avant de l'exécuter. Les chargeurs de modules bien connus utilisés en JavaScript sont les chargeurs de modules CommonJS pour Node.js et require.js pour les applications Web.

Dans TypeScript, comme dans ECMAScript 2015, tout fichier contenant un niveau supérieur import ou export considéré comme un module. A l'inverse, un fichier sans haut niveau import ou export des déclarations est traité comme un script dont le contenu est disponible dans la portée globale (et donc aux modules aussi bien).

Exportation


Exporter une déclaration

Toute déclaration (telle qu'une variable, une fonction, une classe, un alias de type ou une interface) peut être exportée en ajoutant le mot clé export.

Validation.ts

ZipCodeValidator.ts

Déclarations d'exportation

Les instructions d'exportation sont pratiques lorsque les exportations doivent être renommées pour les consommateurs. Ainsi, l'exemple ci-dessus peut être écrit ainsi:

Réexportations

Les modules étendent souvent d'autres modules et exposent partiellement certaines de leurs fonctionnalités. Une réexportation ne l'importe pas localement, ni n'introduit une variable locale.

ParseIntBasedZipCodeValidator.ts

En option, un module peut envelopper un ou plusieurs modules et combiner toutes leurs exportations à l'aide de la syntaxe export * from "module".

AllValidators.ts


Importation

Importer est aussi simple que d'exporter à partir d'un module. Pour importer une déclaration exportée, utilisez l'un des formulaires import ci-dessous :

Importer une seule exportation à partir d'un module

Les importations peuvent également être renommées

Importer le module entier dans une seule variable et l'utiliser pour accéder aux exportations du module

Importer un module pour les effets secondaires uniquement

Bien que cela ne soit pas recommandé, certains modules configurent un état global qui peut être utilisé par d'autres modules. Ces modules peuvent ne pas avoir d'exportations ou le consommateur n'est intéressé par aucune de leurs exportations. Pour importer ces modules, utilisez :


Exportations par défaut

Chaque module peut éventuellement exporter une exportation default. Les exportations par défaut sont marquées avec le mot clé default; et il ne peut y avoir qu'une seule exportation default par module. Les exportations default sont importées en utilisant un formulaire d'importation différent.

Les exportations default sont vraiment utiles. Par exemple, une bibliothèque telle que JQuery peut avoir une exportation par défaut de jQuery ou $, que nous importerions probablement aussi sous le nom $ ou jQuery.

JQuery.d.ts

App.ts

Les classes et les déclarations de fonction peuvent être créées directement en tant qu'exportations par défaut. Les noms de déclaration de classe et de fonction d'exportation par défaut sont facultatifs.

ZipCodeValidator.ts

Test.ts

ou

StaticZipCodeValidator.ts

Test.ts

Les exportations default peuvent aussi n'être que des valeurs :

OneTwoThree.ts

Log.ts


export = et import = require()

CommonJS et AMD ont généralement le concept d'objet exports contenant toutes les exportations d'un module.

Ils prennent également en charge le remplacement de l'objet exports par un objet unique personnalisé. Les exportations par défaut sont censées remplacer ce comportement. Cependant, les deux sont incompatibles. TypeScript prend export = en charge la modélisation des flux de travaux traditionnels CommonJS et AMD.

La syntaxe export= spécifie un seul objet exporté du module. Cela peut être une classe, une interface, un espace de noms, une fonction ou une énumération.

Lors de l'exportation d'un module à l'aide de export=, vous import module = require("module") devez utiliser TypeScript pour importer le module.
ZipCodeValidator.ts

Test.ts


Génération de code pour les modules

Selon la cible de module spécifiée lors de la compilation, le compilateur générera le code approprié pour les systèmes de chargement de modules Node.js(CommonJS), require.js(AMD), UMD, SystemJS ou ECMAScript 2015 (ES6). Pour plus d'informations sur ce que les define, require et les appels register dans le code généré, consultez la documentation pour chaque chargeur de module.

Cet exemple simple montre comment les noms utilisés lors de l'importation et de l'exportation sont traduits dans le code de chargement du module.

SimpleModule.ts

AMD / RequireJS SimpleModule.js

CommonJS / Node SimpleModule.js

UMD SimpleModule.js

Système SimpleModule.js

Modules ECMAScript 2015 natifs SimpleModule.js


Exemple simple

Ci-dessous, nous avons consolidé les implémentations de Validator utilisées dans les exemples précédents pour exporter uniquement une exportation nommée unique à partir de chaque module.

Pour compiler, nous devons spécifier une cible de module sur la ligne de commande. Pour Node.js, utilisez --module commonjs; pour require.js, utilisez --module amd.

Par exemple :

Une fois compilé, chaque module deviendra un fichier séparé .js. Comme avec les balises de référence, le compilateur suivra les importinstructions pour compiler les fichiers dépendants.

Validation.ts

LettersOnlyValidator.ts

ZipCodeValidator.ts

Test.ts


Chargement facultatif de module et autres scénarios de chargement avancés

Dans certains cas, vous souhaiterez peut-être charger un module uniquement dans certaines conditions. Dans TypeScript, nous pouvons utiliser le modèle présenté ci-dessous pour implémenter ce scénario et d'autres scénarios de chargement avancés afin d'appeler directement les chargeurs de module sans perte de sécurité du type.

Le compilateur détecte si chaque module est utilisé dans le code JavaScript émis. Si un identificateur de module est uniquement utilisé dans le cadre d'une annotation de type et jamais en tant qu'expression, aucun appel require n'est émis pour ce module. Cette élision de références inutilisées est une bonne optimisation des performances, et permet également un chargement optionnel de ces modules.

L'idée centrale du modèle est que la import id = require("...") déclaration nous donne accès aux types exposés par le module. Le chargeur de module est invoqué (de manière dynamique require), comme indiqué dans les blocs if ci-dessous. Ceci optimise l'optimisation de référence afin que le module ne soit chargé que lorsque cela est nécessaire. Pour que ce modèle fonctionne, il est important que le symbole défini via un importne soit utilisé que dans les positions de type (c'est-à-dire jamais dans une position qui serait émise dans JavaScript).

Pour maintenir la sécurité de type, nous pouvons utiliser le mot clé typeof. Le mot-clé typeof, lorsqu'il est utilisé dans une position de type, produit le type d'une valeur, en l'occurrence le type du module.

Chargement de module dynamique dans Node.js

Exemple: Chargement de module dynamique dans require.js

Exemple: Chargement de module dynamique dans System.js


Autres bibliothèques JavaScript

Pour décrire la forme des bibliothèques non écrites dans TypeScript, nous devons déclarer l'API exposée par la bibliothèque.

Nous appelons des déclarations qui ne définissent pas une implémentation "ambiante". Généralement, ceux-ci sont définis dans des fichiers .d.ts. Si vous connaissez C/C++, vous pouvez les considérer comme des ichiers .hf.

Regardons quelques exemples.

Modules ambiants

Dans Node.js, la plupart des tâches sont accomplies en chargeant un ou plusieurs modules. Nous pourrions définir chaque module dans son propre fichier .d.ts avec des déclarations d'exportation de niveau supérieur, mais il est plus pratique de les écrire sous la forme d'un fichier .d.ts plus volumineux. Pour ce faire, nous utilisons une construction similaire aux espaces de noms ambiants, mais nous utilisons le mot-clé module et le nom cité du module, qui sera disponible pour une importation ultérieure.

Par exemple :

node.d.ts (extrait simplifié)

Nous pouvons maintenant /// ‹reference› node.d.ts charger les modules avec import url = require("url"); ou import * as URL from "url".

Modules d'ambiance raccourcis

Si vous ne voulez pas prendre le temps d'écrire des déclarations avant d'utiliser un nouveau module, vous pouvez utiliser une déclaration abrégée pour commencer rapidement.

declarations.d.ts

Toutes les importations d'un module abrégé auront le type any.

Déclarations de module génériques

Certains chargeurs de modules, tels que SystemJS et AMD, autorisent l'importation de contenu autre que JavaScript. Ceux-ci utilisent généralement un préfixe ou un suffixe pour indiquer la sémantique de chargement spéciale. Les déclarations de module avec caractères génériques peuvent être utilisées pour couvrir ces cas.

Maintenant, vous pouvez importer des éléments qui correspondent "*!text" ou "json!*".

Modules UMD

Certaines bibliothèques sont conçues pour être utilisées dans de nombreux chargeurs de modules ou sans chargement de module (variables globales). Ceux-ci sont appelés modules UMD. Ces bibliothèques sont accessibles via une variable d'importation ou globale.

Par exemple :

math-lib.d.ts

La bibliothèque peut ensuite être utilisée comme importation dans les modules :

Il peut également être utilisé comme variable globale, mais uniquement à l'intérieur d'un script. (Un script est un fichier sans importations ni exportations.)


Conseils de structuration

Exporter au plus haut niveau possible

Les utilisateurs de votre module devraient avoir le moins de frictions possible lors de l'utilisation des éléments que vous exportez. Ajouter trop de niveaux d'imbrication a tendance à être fastidieux. Réfléchissez bien à la manière dont vous voulez structurer les choses.

L'exportation d'un espace de noms à partir de votre module est un exemple d'ajouter trop de couches d'imbrication. Bien que les espaces de noms aient parfois leur utilité, ils ajoutent un niveau supplémentaire d'indirection lors de l'utilisation de modules. Cela peut rapidement devenir un problème pour les utilisateurs et est généralement inutile.

Les méthodes statiques sur une classe exportée ont un problème similaire: la classe elle-même ajoute une couche d'imbrication. à moins d'augmenter l'expressivité ou l'intention de manière clairement utile, envisagez simplement d'exporter une fonction d'aide.

Si vous n'exportez qu'une class ou plusieurs function, utilisez export default

Tout comme "exporter au plus haut niveau" réduit les frictions sur les consommateurs de votre module, il en va de même pour une exportation par défaut. Si l'objectif principal d'un module est de stocker une exportation spécifique, vous devez envisager de l'exporter en tant qu'exportation par défaut. Cela facilite à la fois l'importation et l'utilisation de l'importation.

Par exemple :

MyClass.ts

MyFunc.ts

Consommateurs

C'est optimal pour les consommateurs. Ils peuvent nommer votre type comme ils veulent (t dans ce cas) et n'ont pas besoin de faire un point excessif pour trouver vos objets.

Si vous exportez plusieurs objets, mettez-les tous au plus haut niveau.

MyThings.ts

Inversement lors de l'importation :

Liste explicite des noms importés
Consommateurs
Utilisez le modèle d'importation d'espace de noms si vous importez un grand nombre d'éléments.

MyLargeModule.ts

Consommateurs

Réexporter pour étendre

Vous aurez souvent besoin d'étendre les fonctionnalités d'un module. Un modèle JS courant consiste à compléter l'objet d'origine avec des extensions, de la même manière que les extensions JQuery. Comme nous l'avons mentionné précédemment, les modules ne fusionnent pas comme le feraient des objets d'espace de noms global. La solution recommandée consiste à ne pas muter l'objet d'origine, mais à exporter une nouvelle entité fournissant la nouvelle fonctionnalité.

Prenons une implémentation simple de la calculatrice définie dans le module Calculator.ts. Le module exporte également une fonction d'assistance pour tester la fonctionnalité de la calculatrice en passant une liste de chaînes d'entrée et en écrivant le résultat à la fin.

Calculatrice.ts

Voici un test simple pour la calculatrice utilisant la fonction test exposée .

TestCalculator.ts

Maintenant, pour étendre ceci pour ajouter un support pour la saisie avec des nombres dans des bases autres que 10, créons ProgrammerCalculator.ts

ProgrammerCalculator.ts

Le nouveau module ProgrammerCalculator exporte une forme d'API similaire à celle du module Calculator d'origine , mais n'augmente aucun objet du module d'origine. Voici un test pour notre classe ProgrammerCalculator :

TestProgrammerCalculator.ts

Ne pas utiliser les espaces de noms dans les modules

Lors du passage à une organisation basée sur des modules, une tendance commune consiste à intégrer les exportations dans une couche d'espaces de noms supplémentaire. Les modules ont leur propre portée et seules les déclarations exportées sont visibles de l'extérieur du module. En gardant cela à l'esprit, l'espace de noms n'apporte que très peu, voire aucune, valeur lorsqu'on travaille avec des modules.

Sur le plan organisationnel, les espaces de noms sont pratiques pour regrouper des objets et des types liés de manière logique dans la portée globale. Par exemple, en C#, vous allez trouver tous les types de collection dans System.Collections. En organisant nos types dans des espaces de noms hiérarchiques, nous fournissons une bonne expérience de "découverte" aux utilisateurs de ces types. Les modules, en revanche, sont déjà nécessairement présents dans un système de fichiers. Nous devons les résoudre par chemin et nom de fichier. Il existe donc un schéma d'organisation logique à utiliser. Nous pouvons avoir un dossier / collections / generic / avec un module de liste.

Les espaces de noms sont importants pour éviter de nommer des collisions dans la portée globale. Par exemple, vous pourriez avoir My.Application.Customer.AddForm et My.Application.Order.AddForm - deux types avec le même nom, mais un espace de noms différent. Ceci, cependant, n'est pas un problème avec les modules. Dans un module, il n'y a aucune raison plausible d'avoir deux objets portant le même nom. Du côté de la consommation, le consommateur d'un module donné doit choisir le nom qu'il utilisera pour faire référence au module, de sorte que les conflits de noms accidentels sont impossibles.

Remarque : Pour plus d'informations sur les modules et les espaces de noms, voir Espaces de noms et modules.

Drapeaux rouges

Tous les éléments suivants sont des drapeaux rouges pour la structuration du module. Vérifiez deux fois que vous n'essayez pas de nommer vos modules externes dans l'espace de noms si l'un de ces cas s'applique à vos fichiers :

  • Un fichier dont la seule déclaration de niveau supérieur est export namespace Foo { ... } (supprime Foo et déplace tout ce qui est "élevé" d'un niveau)
  • Un fichier qui a un seul export class ou export function (envisager d'utiliser export default)
  • Plusieurs fichiers ayant les mêmes export namespace Foo { au plus haut niveau (ne pensez pas qu'ils vont se combiner en un Foo !)