Résolution du module
Remarque : Cette section suppose des connaissances de base sur les modules. Veuillez consulter la documentation sur les modules pour plus d'informations.
La résolution de module est le processus utilisé par le compilateur pour déterminer à quoi une importation fait référence.
Considérons une déclaration d'importation telle que import
{ a } from "moduleA"
; afin de vérifier toute utilisation de
a
,
le compilateur doit savoir exactement ce qu'il représente et devra en vérifier la définition de
moduleA
.
À ce stade, le compilateur demandera "quelle est la forme de
moduleA
?".
Cela peut sembler simple, mais
moduleA
peut être défini dans l'un de vos propres fichiers
.ts
ou
.tsx
ou dans un fichier
.d.ts
dont dépend votre code.
Tout d'abord, le compilateur essaiera de localiser un fichier qui représente le module importé.
Pour ce faire, le compilateur suit l'une des deux stratégies suivantes: classique ou noeud.
Ces stratégies indiquent au compilateur où chercher
moduleA
.
Si cela ne fonctionne pas et si le nom du module n'est pas relatif (et dans le cas de "
moduleA
", c'est le cas), le compilateur tentera de localiser une déclaration de module ambiante.
Nous couvrirons ensuite les importations non relatives.
Enfin, si le compilateur n'a pas pu résoudre le module, il enregistre une erreur.
Dans ce cas, l'erreur serait quelque chose comme
error TS2307 : Cannot find module 'moduleA'
.
Importations de modules relatives ou non relatives
Les importations de modules sont résolues différemment selon que la référence de module est relative ou non relative.
Une importation relative est celle qui commence par
/
,
./
ou
../
.
Quelques exemples incluent :
import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";
Toute autre importation est considérée comme non relative.
Quelques exemples incluent :
import * as $ from "jquery";
import { Component } from "@angular/core";
Une importation relative est résolue par rapport au fichier d'importation et ne peut pas être résolue en une déclaration de module ambiante.
Vous devez utiliser des importations relatives pour vos propres modules afin de garantir leur emplacement relatif au moment de l'exécution.
Une importation non relative peut être résolue par rapport à
baseUrl
,
ou par le biais d'un mappage de chemin, que nous couvrirons ci-dessous.
Ils peuvent également résoudre des déclarations de module ambiant.
Utilisez des chemins non relatifs lors de l'importation de vos dépendances externes.
Stratégies de résolution de module
Il existe deux stratégies de résolution de module possibles:
Noeud
et
Classic
.
Vous pouvez utiliser le drapeau
--moduleResolution
pour spécifier la stratégie de résolution du module.
Si non spécifié, la valeur par défaut est classique pour
--module AMD | System | ES2015
ou noeud autrement.
Classique
C'était la stratégie de résolution par défaut de
TypeScript
.
De nos jours, cette stratégie est principalement présente pour la compatibilité ascendante.
Une importation relative sera résolue par rapport au fichier d'importation.
Ainsi,
import { b } from "./moduleB"
dans le fichier source,
/root/src/folder/A.ts
les recherches suivantes seront effectuées:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
Pour les importations de modules non relatives, cependant, le compilateur parcourt l'arborescence en commençant par le répertoire
contenant le fichier d'importation, en essayant de localiser un fichier de définition correspondant.
Par exemple :
Une importation non relative vers
moduleB
tel que import
{ b } from "moduleB"
, par exemple, dans un fichier source
/root/src/folder/A.ts
,
entraî nerait une tentative de localisation des emplacements suivants "
moduleB
" :
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
Noeud
Cette stratégie de résolution tente d'imiter le mécanisme de résolution du module
Node.js
lors de l'exécution.
Le plein algorithme de résolution
Node.js
est décrit dans la documentation du module
Node.js
.
Comment Node.js résout les modules
Pour comprendre les étapes que le compilateur TS, il est important de faire la lumière sur les modules
Node.js
.
Traditionnellement, les importations dans
Node.js
sont effectuées en appelant une fonction nommée require.
Le comportement que prend
Node.js
diffère selon que l'on require l'attribue un chemin relatif ou non.
Les chemins relatifs sont assez simples. A titre d'exemple, considérons un fichier situé à l'emplacement
/root/src/moduleA.js
qui contient l'importation.
var x = require("./moduleB");
,
Node.js
résout cette importation dans l'ordre suivant :
Demandez le fichier nommé
/root/src/moduleB.js
, s'il existe.
Demandez au dossier
/root/src/moduleB
s'il contient un fichier nommé
package.js
en spécifiant un module
"main"
.
Dans notre exemple, si
Node.js
a trouvé le fichier
/root/src/moduleB/package.js
concontenant
{ "main": "lib/mainModule.js" }
,
alors
Node.js
se référera à
/root/src/moduleB/lib/mainModule.js
.
Demandez au dossier
/root/src/moduleB
s'il contient un fichier nommé
index.js
.
Ce fichier est implicitement considéré comme le module "principal" de ce dossier.
Vous pouvez en savoir plus à ce sujet dans la documentation de
Node.js
sur les modules de fichiers et les modules de dossiers.
Cependant, la résolution d'un nom de module non relatif est effectuée différemment.
Node cherchera vos modules dans des dossiers spéciaux nommés
node_modules
.
Un dossier
node_modules
peut être au même niveau que le fichier actuel ou plus haut dans la chaî ne de répertoires.
Node va parcourir la chaî ne de répertoires, en parcourant chaque répertoire
node_modules
jusqu'à trouver le module que vous avez essayé de charger.
En reprenant notre exemple ci-dessus, demandez-vous si vous
/root/src/moduleA.js
avez plutôt utilisé un chemin non relatif et importé
var x = require("moduleB");
.
Le noeud essaierait alors de résoudre
moduleB
avec chacun des emplacements jusqu'à ce que l'un d'entre eux travaille.
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
(s'il spécifie une "main"propriété)
/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
(s'il spécifie une "main"propriété)
/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
(s'il spécifie une "main"propriété)
/node_modules/moduleB/index.js
Notez que Node.js a sauté un répertoire dans les étapes (4) et (7).
Vous pouvez en savoir plus sur le processus dans la documentation de
Node.js
sur le chargement de modules à partir de
node_modules
.
Comment TypeScript résout les modules
TypeScript
imitera la stratégie de résolution d'exécution
Node.js
afin de localiser les fichiers de définition des modules au moment de la compilation.
Pour ce faire, il recouvre les extensions de fichier source dactylographiée (
.ts, .tsxet .d.ts
) sur la logique de la résolution du noeud.
TypeScript
utilisera également un champ dans
package.json
,
named "types"
pour refléter son objectif "
main
".
Le compilateur l'utilisera pour trouver le fichier de définition "principal" à consulter.
Par exemple, une instruction d'importation telle que
import { b } from "./moduleB" in /root/src/moduleA.ts
entraînerait la tentative de localisation des emplacements suivants "
./moduleB
" :
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
(s'il spécifie une "types"propriété)
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
Rappelez-vous que
Node.js
a recherché un fichier nommé
moduleB.js
, puis un applicable
package.json
, puis un
index.js
.
De la même manière, une importation non relative suivra la logique de résolution de
Node.js
, recherchant d'abord un fichier, puis un dossier applicable.
Ainsi,
import { b } from "moduleB"
dans le fichier source,
/root/src/moduleA.ts
les recherches suivantes seront effectuées:
/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json
(s'il spécifie une "types"propriété)
/root/src/node_modules/@types/moduleB.d.ts
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts
/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json
(s'il spécifie une "types"propriété)
/root/node_modules/@types/moduleB.d.ts
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts
/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json
(s'il spécifie une "types"propriété)
/node_modules/@types/moduleB.d.ts
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
Ne vous laissez pas intimider par le nombre d'étapes indiquées;
TypeScript
ne fait que sauter des répertoires deux fois aux étapes (9) et (17).
Ce n'est vraiment pas plus complexe ce que
Node.js
fait lui-même.
Drapeaux de résolution de module supplémentaires
Une mise en page source du projet ne correspond parfois pas à celle de la sortie.
Généralement, un ensemble d'étapes de construction génère la sortie finale.
Celles-ci incluent la compilation de v
.ts
et la copie
.js
de dépendances depuis différents emplacements source vers un seul emplacement de sortie.
Le résultat n'est que les modules au moment de l'exécution peuvent avoir des noms différents de ceux des fichiers source contenant leurs définitions.
Ou bien les chemins de module dans la sortie finale peuvent ne pas correspondre à leurs chemins de fichier source correspondants au moment de la compilation.
Le compilateur
TypeScript
dispose d'un ensemble d'indicateurs supplémentaires pour informer le compilateur des transformations attendues des sources afin de générer la sortie finale.
Il est important de noter que le compilateur ne réaliser ces transformations;
il utilise simplement ces informations pour guider le processus de résolution
d'une importation de module dans son fichier de définition.
URL de base
L'utilisation de a
baseUrl
est une pratique courante dans les applications utilisant des chargeurs de modules
AMD
dans lesquels les modules sont "déployés" dans
un seul dossier au moment de l'exécution. Les sources de ces modules peuvent vivre dans des répertoires différents, mais un script de construction les mettra tous ensemble.
Ce paramètre
baseUrl
indique au compilateur où trouver les modules.
Toutes les importations de modules avec des noms non relatifs sont supposées être relatives à
baseUrl
.
La valeur de
baseUrl
est déterminée comme suit :
- valeur de l'argument de ligne de commande
baseUrl
(si le chemin d'accès est relatif, il est calculé en fonction du répertoire en cours)
- valeur de la propriété
baseUrl
dans 'tsconfig.json'
(si le chemin indiqué est relatif, il est calculé en fonction de l'emplacement de 'tsconfig.json
')
Notez que les importations de modules relatifs ne sont pas affectées par la définition de
baseUrl
, car elles sont toujours résolues par rapport à leurs fichiers d'importation.
Vous trouverez plus de documentation sur
baseUrl
dans les documentations
RequireJS
et
SystemJS
.
Mappage de chemin
Parfois, les modules ne sont pas directement situés sous
baseUrl
.
Par exemple, une importation dans un module "
jquery
" serait traduite lors de l'exécution en
"node_modules/jquery/dist/jquery.slim.min.js"
.
Les chargeurs utilisent une configuration de mappage pour mapper les noms de module sur des fichiers au moment de l'exécution,
voir Documentation de
RequireJs
et le documentation de
SystemJS
.
Le compilateur
TypeScript
prend en charge la déclaration de tels mappages à l'aide de "
paths
" propriétés dans des fichiers
tsconfig.json
.
Voici un exemple pour savoir comment spécifier la propriété
jquery "paths"
.
Noter que "
paths
" sont résolus par rapport à "
baseUrl
".
Lorsque vous définissez "
baseUrl
" une autre valeur que celle "
.
" du répertoire
tsconfig.json
,
les mappages doivent être modifiés en conséquence. Supposons que vous définissiez
"baseUrl" : "./src"
dans l'exemple ci-dessus,
puis
jquery
devrait être mappé
"../node_modules/jquery/dist/jquery"
.
L'utilisation
"paths"
permet également des mappages plus sophistiqués comprenant plusieurs emplacements de secours.
Envisagez une configuration de projet dans laquelle seuls certains modules sont disponibles dans un emplacement et les autres dans un autre.
Une étape de construction les mettrait tous ensemble au même endroit. La mise en page du projet peut ressembler à:
Le correspondant
tsconfig.json
ressemblerait à :
Ceci indique au compilateur pour toute importation de module qui correspond au modèle
"*"
(c'est-à-dire toutes les valeurs) de rechercher dans deux emplacements :
"*"
: signifiant le même nom inchangé, alors map ‹moduleName› => ‹baseUrl› / ‹moduleName›
"generated/*"
signifiant le nom du module avec le préfixe ajouté "généré", donc map ‹moduleName› => ‹baseUrl› /generated/ ‹moduleName›
En suivant cette logique, le compilateur tentera de résoudre les deux importations en tant que telles :
importer 'dossier1 / fichier2'
- le motif
'*'
est apparié et le caractère générique capture le nom du module complet
- essayez la première substitution dans la liste:
'*' -> folder1/file2
- Le résultat de la substitution est un nom non relatif combinez-le avec
baseUrl -> projectRoot/folder1/file2.ts
.
- Le fichier existe.
- Terminé.
importer 'dossier2 / fichier3'
- le motif
'*'
est apparié et le caractère générique capture le nom du module complet
- essayez la première substitution dans la liste:
'*' -> folder2/file3
- Le résultat de la substitution est un nom non relatif combinez-le avec
baseUrl -> projectRoot/folder2/file3.ts
.
- Le fichier n'existe pas, passez à la deuxième substitution
- deuxième substitution 'généré
/ *' -> generated/folder2/file3
- Le résultat de la substitution est un nom non relatif combinez-le avec
baseUrl -> projectRoot/generated/folder2/file3.ts
.
- Le fichier existe.
- Terminé.
Répertoires virtuels avec rootDirs
Parfois, les sources de projet de plusieurs répertoires au moment de la compilation sont toutes combinées pour générer un seul répertoire de sortie.
Cela peut être vu comme un ensemble de répertoires sources créant un répertoire
"virtuel"
.
En utilisant
'rootDirs'
, vous pouvez informer le compilateur des racines constituant ce répertoire
"virtuel"
; ainsi,
le compilateur peut résoudre les importations de modules relatifs dans ces répertoires
"virtuels"
comme si elles étaient fusionnées dans un répertoire.
Par exemple, considérons cette structure de projet :
Les fichiers
src/views
sont des codes d'utilisateur pour certains contrôles de l'interface utilisateur.
Les fichiers
generated/templates
sont des codes de liaison de modèles d'interface utilisateur générés automatiquement par un générateur de modèles dans le cadre de la construction.
Une étape de construction copiera les fichiers dans
/src/views
et
/generated/templates/views
dans le même répertoire dans la sortie.
Au moment de l'exécution, une vue peut s'attendre à ce que son modèle existe à côté d'elle et doit donc l'importer sous un nom relatif en tant que
"./template"
.
Pour spécifier cette relation avec le compilateur, utilisez
"rootDirs"
.
"rootDirs"
spécifiez une liste de racines dont le contenu doit fusionner au moment de l'exécution.
En suivant notre exemple, le fichier
tsconfig.json
devrait ressembler à ceci :
Chaque fois que le compilateur voit une importation relative de module dans un sous-dossier de l'un des
rootDirs
,
il tentera de rechercher cette importation dans chacune des entrées de
rootDirs
.
La flexibilité de
rootDirs
n'est pas limitée à la spécification d'une liste de répertoires sources physiques qui sont fusionnés de manière logique.
Le tableau fourni peut inclure un nombre quelconque de noms de répertoires ad hoc et arbitraires, qu'ils existent ou non.
Cela permet au compilateur de capturer de manière sécurisée les fonctions complexes de groupement et d'exécution telles que l'inclusion
conditionnelle et les plug-ins de chargeur spécifiques au projet.
Prenons un scénario d'internationalisation dans lequel un outil de génération génère automatiquement des ensembles
spécifiques à l'environnement local en interpolant un jeton de chemin d'accès spécial, par exemple
#{locale}
,
dans le cadre d'un chemin de module relatif, tel que
./#{locale}/messages
. Dans cette configuration hypothétique,
'outil énumère les paramètres régionaux pris en charge, mappant le chemin abstrait en
./zh/messages
,
./de/messages
etc...
Supposons que chacun de ces modules exporte un tableau de chaînes.
Par exemple
./zh/messages
pourrait contenir :
En tirant parti de cette situation,
rootDirs
nous pouvons informer le compilateur de ce mappage et lui permettre ainsi de le
résoudre en toute sécurité
/#{locale}/messages
, même si le répertoire n'existera jamais.
Par exemple, avec ce qui suit
tsconfig.json
:
Le compilateur va maintenant résoudre le problème
import messages from './#{locale}/messages'
,
import messages from './zh/messages'
à des fins d'outillage,
permettant ainsi un développement indépendant des paramètres régionaux sans compromettre la prise en charge de la conception.
Résolution du module de traçage
Comme indiqué précédemment, le compilateur peut visiter des fichiers situés en dehors du dossier actuel lors de la résolution d'un module.
Cela peut s'avérer difficile lors du diagnostic de la raison pour laquelle un module n'est pas résolu ou est résolu en une définition incorrecte.
L'activation du suivi de la résolution du module du compilateur à l'aide de
--traceResolution
donne un aperçu de ce qui s'est passé pendant le processus de résolution du module.
Disons que nous avons un exemple d'application qui utilise le module
typescript
app.ts
a une importation comme
import * as ts from "typescript"
.
Invoquer le compilateur avec
--traceResolution
Résultats dans une sortie telle que :
Choses à surveiller
Nom et lieu de l'importation
======== Résolution du module
'typescript'
à partir de
'src/app.ts'
. ========
La stratégie suivie par le compilateur
Le type de résolution de module n'est pas spécifié, en utilisant
'NodeJs'
.
Chargement des types à partir des paquets npm
'package.json'
a le champ
'types' './lib/typescript.d.ts'
qui fait référence à
'node_modules/typescript/lib/typescript.d.ts'
.
Résultat final
======== Le nom de module
'typescript'
a été résolu avec succès en
'node_modules/typescript/lib/typescript.d.ts'
. ========
En utilisant --noResolve
Normalement, le compilateur essaiera de résoudre toutes les importations de modules avant de démarrer le processus de compilation.
Chaque fois qu'il résout avec succès importun fichier, ce fichier est ajouté à l'ensemble des fichiers que le compilateur traitera ultérieurement.
Les --noResolveoptions du compilateur indiquent au compilateur de ne pas "ajouter" à la compilation des fichiers qui n'ont pas été transmis sur la ligne de commande.
Il tentera toujours de résoudre le module en fichiers, mais si le fichier n'est pas spécifié, il ne sera pas inclus.
Par exemple :
app.ts
Compiler en
app.ts
utilisant
--noResolve
devrait aboutir à :
- Trouver correctement
moduleA
comme il a été passé sur la ligne de commande.
- Erreur pour ne pas trouver
moduleB
car il n'a pas été passé.
Questions courantes
Pourquoi un module de la liste d'exclusion est-il toujours choisi par le compilateur ?
tsconfig.json
transforme un dossier en
"projet"
. Sans spécifier une entrées
"exclude"
ou les
"files"
, tous les fichiers dans le dossier contenant le
tsconfig.json
et
tous ses sous-répertoires sont inclus dans votre compilation. Si vous souhaitez exclure certains des fichiers utilisés
"exclude"
,
si vous préférez spécifier tous les fichiers plutôt que de laisser le compilateur les rechercher, utilisez
"files"
.
C'était l'inclusion automatique
tsconfig.json
.
Cela n'intègre pas la résolution du module comme indiqué ci-dessus.
Si le compilateur a identifié un fichier en tant que cible d'une importation de module, il sera inclus dans la compilation, qu'il ait été exclu ou non aux étapes précédentes.
Ainsi, pour exclure un fichier de la compilation, vous devez l'exclure, ainsi que tous les fichiers contenant une directive importou
/// ‹reference path="..." /›
.