Vos recrutements informatiques

700 000 développeurs, chefs de projets, ingénieurs, informaticiens...

Contactez notre équipe spécialiste en recrutement

Importation de modules externes en TypeScript

Le , par yahiko, Rédacteur/Modérateur

Sommaire

1. Introduction
2. Mise en place de l'environnement
3. Squelette du projet
4. Importation d'un module externe TypeScript
5. Importation d'un module externe ES2015
6. Importation d'un fichier JavaScript à plat
7. Importation de bibliothèques JavaScript courantes

1. Introduction

TypeScript est un surensemble typé de JavaScript. C'est un langage open source conçu par Anders Hejlsberg qui a déjà les langages Turbo Pascal, Delphi et C# à son actif, excusez du peu. Nous supposerons dans cet article que le lecteur a déjà quelques notions de ce langage dont il pourra lire une introduction ici.

Cet article a pour objectif de faire un rapide tour d'horizon sur les possibilités d'importation des modules externes dans un projet TypeScript, ce qui est souvent un point de blocage lors de la découverte de ce langage.

Par importation de modules externes, nous entendons importation dynamique de scripts et non pas d'inclusion de scripts via la balise HTML <script>.

Il existe différents formats de modules JavaScript actuellement. TypeScript prend en charge les formats AMD, CommonJS, SystemJS, UMD et ES2015 (ES6). Nous supposerons également le lecteur familiarisé avec ces formats, et notamment AMD puisque c'est celui-ci qui nous servira de référence tout au long de notre présentation.

Après avoir décrit le contexte de travail dans lequel se situeront nos exemples (arborescence projet, outils, configuration), nous présenterons trois cas standards d'importation de modules. Un premier cas pour le format natif de module dans TypeScript. Un second pour le format JavaScript/ES2015, mais dont la méthode peut s'appliquer aussi pour les formats de modules précités. Et enfin un troisième cas où nous n'importerons pas vraiment des modules, mais des scripts à plat.

Enfin, nous terminerons cette présentation par un exemple d'importation des bibliothèques jQuery et underscore.

A noter que tous les exemples de cet article peuvent être récupérés sur mon dépôt GitHub à l'adresse suivante : https://github.com/yahiko00/ImportationModules

2. Mise en place de l'environnement

2.1. Arborescence du projet

Tout au long de cet article, nous supposerons avoir l'arborescence suivante pour notre projet :
Projet/
build/ ...
node_modules/
@types/ ...
requirejs/
require.js
...
script/
app.ts
...
index.html
tsconfig.json


Le répertoire Projet/ constituera la racine du projet où on trouvera les fichiers index.html, notre page Web, et tsconfig.json que nous verrons plus en détail un peu plus loin ; le répertoire build/ sera là où sera généré le résultat de la compilation de TypeScript ; le répertoire node_modules/, généré par l'utilitaire npm, contiendra les différentes bibliothèques dont RequireJS qui permettra d'importer les modules au format AMD ; et le répertoire script/ contiendra nos scripts.

2.2. Installation des outils

EDI

Le compilateur TypeScript est intégré à Visual Studio 2015 ce qui permet de s'initier à ce langage très rapidement. C'est aussi le cas de WebStorm qui est une référence dans les EDI orientés Web. Pour ce projet, nous utiliserons un éditeur plus léger ce qui aura le mérite de nous forcer à mettre un peu les mains dans le cambouis du fichier projet tsconfig.json. Il existe pléthore d'éditeurs sur le marché. Le choix ici se portera sur Visual Studio Code dont l'interface graphique a été codée en TypeScript justement. Il a l'avantage d'être léger contrairement au mastodonte qu'est Visual Studio, multiplateformes (Windows, OS X et Linux), et offre un bon confort de programmation dans la mesure où il prend en charge le Language Service, l'API de TypeScript lui permettant de bénéficier entre autre de la coloration syntaxique, de l'autocomplétion intelligente, du refactoring ou la détection des erreurs à la volée. Toujours est il que ce qui sera présenté dans cet article restera globalement valable pour n'importe quel environnement de développement.

nodeJS
Si ce n'est pas déjà fait, il sera préférable d'installer NodeJS rien que pour son utilitaire de téléchargement de packages npm qui nous servira ici à installer le compilateur TypeScript et des bibliothèques JavaScript dans le dernier chapitre.

Compilateur TypeScript
Pour installer la version de production de TypeScript (normalement déjà livrée avec Visual Studio Code), la ligne de commande est la suivante :
npm install -g typescript

Pour installer la version nigthly, en cours de développement, la ligne de commande est la suivante :
npm install -g typescript@next

A l'heure où est écrit cet article, la version 2.0 de TypeScript est en phase beta. Pour la télécharger, la ligne de commande est la suivante :
npm install -g typescript@beta

Il est possible et recommandé de vérifier la version du compilateur via la ligne de commande suivante :
tsc -v

C'est avec cette version beta 2.0 sur laquelle seront basés les exemples de cet article. A noter que plusieurs versions du compilateur TypeScript peuvent très bien cohabiter, à condition de bien savoir quelle version est lancée à quel moment. Pour spécifier la version de TypeScript à prendre en compte par Visual Studio Code, on peut utilement se référer à cet article.

RequireJS

Pour des raisons de simplicité, nos exemples seront basés sur les modules au format AMD dans un micro projet côté client. C'est pourquoi nous opterons ici pour la bibliothèque d'importation des modules RequireJS. Pour installer cette bibliothèque, voici la ligne de commande à exécuter à la racine Projet/ :
npm install requirejs

Suite à cela, nous devrions trouver le fichier require.js dans le répertoire Projet/node_modules/requirejs/.

Afin que TypeScript puisse correctement exploiter cette bibliothèque écrite en JavaScript, il est nécessaire de lui fournir un fichier de définitions de types au format .d.ts. Heureusement, pour la plupart des bibliothèques et frameworks un minimum diffusés, il existe de tels fichiers de définition prêts à l'emploi. Grâce à la version 2.0, une simple commande npm suffit à rapatrier le fichier de définition dont nous avons besoin :
npm install @types/requirejs

Cela téléchargera le fichier de définition .d.ts pour RequireJS dans le répertoire standard Projet/node_modules/@types/. Si notre éditeur exploite correctement la fonctionnalité de découverte automatique des fichiers de définitions de types fournie par l'API du compilateur, et c'est le cas notamment pour Visual Studio Code, alors nous n'avons rien d'autre à faire pour utiliser RequireJS dans notre projet TypeScript. C'est vraiment un gain de temps très appréciable comparé à la méthode DefinitelyTyped pour les connaisseurs.

2.3. Configuration

La configuration du compilateur TypeScript peut s'effectuer à différents endroits. Au niveau de l'EDI comme c'est le cas pour Visual Studio par exemple, au niveau de la ligne de commande avec les options de compilation ou au niveau d'un fichier projet qui se nomme par défaut tsconfig.json. Ce fichier doit se trouver à la racine du projet, dans notre cas Projet/. Un squelette de ce fichier projet peut être généré par le compilateur via la ligne de commande suivante :
tsc --init

Pour les besoins de cet article, voici ce que doit contenir ce fichier dans un premier temps :
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// tsconfig.json 
  
{ 
    "compilerOptions": { 
        "noImplicitAny": true, 
        "strictNullChecks": true, 
        "target": "es5", 
        "module": "amd", 
        "outDir": "./build" 
    }, 
    "exclude" : [ 
        "./build", 
        "./node_modules" 
    ] 
}

  • Dans la section compilerOptions, l'option noImplicitAny indique que les déclarations non typées, comme par exemple let x;, ne seront pas permises.
  • L'option strictNullChecks qui vient d'arriver avec la version 2.0 indique que nous ne manipulons que des types non-nullifiables (cf. récapitulatif de la version 2.0).
  • L'option target permet de définir la version ECMAScript que doit générer TypeScript. Par défaut, c'est la version ES5.
  • L'option module permet de définir le format des modules à gérer par le compilateur aussi bien à l'importation qu'à la génération. Ici nous travaillerons avec le format AMD, mais les formats CommonJS, SystemJS, UMD et ES2015 (ES6) sont également supportés.
  • L'option outDir définit un répertoire vers lequel seront générés les résultats de la compilation.
  • La section exclude définit les répertoires et les fichiers à exclure du projet. Ici, on souhaite ignorer les fichiers se trouvant dans le répertoire de sortie build/ et les bibliothèques rapatriées via npm, comme RequireJS par exemple, qui se retrouveront dans le répertoire node_modules/.


Pour avoir la liste exhaustive des options disponibles dans tsconfig.json, on peut simplement afficher l'aide du compilateur via la commande tsc -h, dans la mesure où tsconfig.json reprend toutes les options du compilateur, à de rares exceptions près. On peut également se reporter à la page suivante, même si sa mise à jour est souvent en décalage avec la dernière version de TypeScript.

3. Squelette du projet

Le point d'entrée de notre projet minimaliste sera une page Web pratiquement vide, index.html, dont le but sera juste de charger la bibliothèque RequireJS qui lancera un fichier JavaScript config.js (aussi souvent appelé main.js) qui aura pour responsabilité de charger les différents modules et dépendances du projet et de lancer l'application proprement dite.

Code html : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html> 
<html> 
    <meta charset="utf-8">  
    <head> 
        <title>Importation de modules externes en TypeScript</title> 
        <script data-main="build/config.js" src="node_modules/requirejs/require.js"></script> 
    </head> 
    <body> 
        <h1>Importation de modules externes en TypeScript</h1> 
    </body> 
</html>
Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config.ts 
  
require( 
    ['app'], 
  
    function (app: any) { 
        app.start(); 
    }, 
  
    function (err: any) { 
        console.error('ERROR: ', err.requireType); 
        console.error('MODULES: ', err.requireModules); 
    } 
);

Ce fichier config.ts (en réalité, config.js lorsqu'il sera compilé en JavaScript) lancera le programme app.ts (en réalité, app.js lorsqu'il sera compilé en JavaScript) via une méthode start().

Sur le site officiel de RequireJS, on trouvera toutes les informations utiles concernant la structure de ce programme d'initialisation et sur les modules AMD en général.

4. Importation d'un module externe TypeScript

4.1. Programme principal

Dans un premier exemple, nous allons voir comment importer dans notre petite application app.ts un module externe au format natif de TypeScript. C'est le cas le plus simple.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
// app.ts 
  
import * as Animal from "animal"; // Module TS 
  
export function start() { 
    let garry = new Animal.Snail('Garry'); 
    let simbad = new Animal.Lion('Simbad', Animal.Sex.MALE); 
  
    console.log(garry.name); 
    console.log(simbad.name, simbad.sex); 
}

Dans le code ci-dessus, nous pouvons distinguer deux parties. La première avec l'instruction import qui demande au compilateur de rechercher un module externe animal.ts et de rapatrier tous les membres accessibles (exportés) dans une nouvelle variable Animal. Cette syntaxe est celle de la nouvelle norme ES2015 mise en place depuis la version 1.5 de TypeScript. Même si l'ancienne est toujours possible, import Animal = require("animal"), il est préférable d'utiliser la nouvelle syntaxe, quand c'est possible. Ce qui ne sera pas toujours le cas, comme nous le verrons par la suite.

Notons que n'avons pas précisé de chemin pour le module animal. On parle dans ce cas d'importation non-relative. Dans le mode de résolution des modules par défaut (--moduleResolution classic), le compilateur cherche tout d'abord le module à importer animal.ts dans le même répertoire que le programme appelant app.ts, puis remonte l'arborescence, parent après parent, jusqu'à la racine /.

Si nous avions écrit import * as Animal from "./animal", alors nous aurions eu affaire à une importation relative. Et dans le mode de résolution des modules par défaut, le compilateur aurait cherché le module à importer animal.ts dans le même répertoire que le programme appelant app.ts, et c'est tout.

Il existe de nombreuses variations à la résolution de module. Les traiter en détail dans cet article nous emmènerait trop loin. Mentionnons simplement qu'il existe un autre mode de résolution des modules basé sur la logique de nodeJS (--moduleResolution node). Et que la version 2.0 a introduit les options de compilation --baseUrl, --paths et --rootDirs, également disponibles dans le fichier projet tsconfig.json, qui permettent d'avoir un large contrôle sur l'emplacement des modules.

La seconde partie avec export, indique au compilateur TypeScript que le fichier app.ts doit également être considéré comme un module (et donc transformé en conséquence lors de la génération en JavaScript) et que la fonction start() sera accessible par les programmes qui importeront ce fichier, comme c'est le cas par exemple du programme de chargement des modules AMD config.ts.

Jetons maintenant un œil sur le module animal.ts.

4.2. Module externe TypeScript

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// animal.ts 
  
export abstract class Animal { 
    name: string; 
  
    constructor(name: string) { 
      this.name = name; 
    } 
  
    abstract shout(): string; 
} 
  
export class Snail extends Animal { 
    shout(): string { 
        return '...'; 
    } 
} 
  
export enum Sex { MALE, FEMALE }; 
  
export class Lion extends Animal { 
    sex: Sex; 
  
    constructor(name: string, sex: Sex) { 
        super(name); 
        this.sex = sex; 
    } 
  
    shout(): string { 
        return 'Rooooaarrr!' 
    } 
}

Le code ci-dessus n'a rien de bien particulier. Il s'agit juste pour l'exemple d'une petite hiérarchie de classes où sont exportées via l'instruction export, les classes Animal, Snail et Lion, ainsi que l'énumération Sex.

4.3. Résultat de la compilation

Pour compiler notre projet, il suffit de se placer à la racine de notre projet, ie. le répertoire Projet/, au même niveau que le fichier tsconfig.json en fait, et de lancer tout simplement tsc en ligne de commande.

A l'issue de la compilation, voici l'arborescence que nous devrions obtenir :

Projet/
build/
animal.js
app.js
config.js
node_modules/
@types/ ...
requirejs/
require.js
...
script/
animal.ts
app.ts
config.ts
index.html
tsconfig.json


Comme attendu, nos trois fichiers TypeScript .ts dans le répertoire script/ ont été transpilés en fichiers JavaScript ES5 .js dans le répertoire build/.

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
// app.js 
  
define(["require", "exports", 'animal'], function (require, exports, Animal) { 
    "use strict"; 
    function start() { 
        var garry = new Animal.Snail('Garry'); 
        var simbad = new Animal.Lion('Simbad', Animal.Sex.male); 
        console.log(garry.name); 
        console.log(simbad.name, simbad.sex); 
    } 
    exports.start = start; 
});

Ci-dessus, nous pouvons remarquer que la dépendance d'importation du module animal a bien été prise en compte dans la fonction define() lors de la transformation du fichier app.ts en module AMD.

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// animal.js 
  
var __extends = (this && this.__extends) || function (d, b) { 
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 
    function __() { this.constructor = d; } 
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 
}; 
define(["require", "exports"], function (require, exports) { 
    "use strict"; 
    var Animal = (function () { 
        function Animal(name) { 
            this.name = name; 
        } 
        return Animal; 
    }()); 
    var Snail = (function (_super) { 
        __extends(Snail, _super); 
        function Snail() { 
            _super.apply(this, arguments); 
        } 
        Snail.prototype.shout = function () { 
            return '...'; 
        }; 
        return Snail; 
    }(Animal)); 
    exports.Snail = Snail; 
    (function (Sex) { 
        Sex[Sex["male"] = 0] = "male"; 
        Sex[Sex["female"] = 1] = "female"; 
    })(exports.Sex || (exports.Sex = {})); 
    var Sex = exports.Sex; 
    ; 
    var Lion = (function (_super) { 
        __extends(Lion, _super); 
        function Lion(name, sex) { 
            _super.call(this, name); 
            this.sex = sex; 
        } 
        Lion.prototype.shout = function () { 
            return 'Rooooaarrr!'; 
        }; 
        return Lion; 
    }(Animal)); 
    exports.Lion = Lion; 
});

Le fichier animal.js est la transpilation ES5/AMD du module animal.ts.
Enfin, pour la forme le fichier config.js ci-dessous, mais qui est pratiquement identique à sa source TypeScript.

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
// config.js 
  
require(['app'], function (app) { 
    app.start(); 
}, function (err) { 
    console.error('ERROR: ', err.requireType); 
    console.error('MODULES: ', err.requireModules); 
});

On constate donc qu'il est tout à fait possible et même très facile d'intégrer sous la forme de modules un sous-projet TypeScript à un projet plus global basé lui sur du JavaScript. Maintenant, examinons comment faire l'inverse, à savoir intégrer des modules JavaScript à une application TypeScript.

5. Importation d'un module externe ES2015

5.1. Module externe JavaScript ES1025

Il est tout à fait possible d'importer des modules externes écrits nativement en JavaScript.

Considérons en entrée le module animal.js suivant :

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// animal.js 
  
export class Animal { 
    constructor(name) { 
      this.name = name; 
    } 
  
    shout() {} 
} 
  
export class Snail extends Animal { 
    shout() { 
        return '...'; 
    } 
} 
  
export const Sex = { MALE: 0, FEMALE: 1 }; 
  
export class Lion extends Animal { 
    constructor(name, sex) { 
        super(name); 
        this.sex = sex; 
    } 
  
    shout() { 
        return 'Rooooaarrr!' 
    } 
}

Tout d'abord, il s'agit bien d'un fichier JavaScript, et pas d'un fichier TypeScript. Il est simplement écrit avec la norme ES2015 et nous permet de bénéficier de la syntaxe des classes et surtout dans notre situation de la nouvelle syntaxe des modules, identique à celle de TypeScript, plus intuitive à mon sens que les formats existants AMD, CommonJS ou UMD. A la charge du compilateur TypeScript de faire tout seule la transpilation en ES5 et d'emballer le code dans le format de module de notre choix, sans nécessiter un autre transpileur comme Babel.

Cependant, comme il n'y a pas de magie, nous devons fournir une définition de type à ce fichier JavaScript. Cela n'a rien de très compliqué, d'autant qu'ici nous avons une forme déjà proche de TypeScript. Il ne manque finalement que les annotations de types.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// animal.d.ts 
  
export declare abstract class Animal { 
    name: string; 
    constructor(name: string); 
    abstract shout(); 
} 
  
export declare class Snail extends Animal { 
    shout(); 
} 
  
export declare enum Sex { MALE, FEMALE } 
  
export declare class Lion extends Animal { 
    sex: Sex; 
    constructor(name: string, sex: Sex); 
    shout(); 
}

Le mot-clé déclare indique au compilateur que ce qui suit existera bel et bien au moment de l'exécution, mais n'est pas immédiatement disponible pour le compilateur. C'est en effet notre cas puisque la concrétisation de ce qui est décrit dans ce fichier est au format JavaScript, et dont le contenu ne sera pleinement accessible qu'à l'exécution, au sein de la page HTML. On peut voir ça comme une sorte de promesse faite par le développeur envers le compilateur. Une analogie avec le langage C/C++ serait le mot-clé extern.

Depuis la version 2.0 de TypeScript, nous aurions pu nous passer d'un fichier de définition de types comme ci-dessus. Nous aurions bien eu accès aux classes et à leurs propriétés, mais ces dernières seraient restées non typées (any). Cela peut être une approche valable lors d'une première prise en charge d'un module JavaScript, même si à terme, il reste préférable de passer par un fichier de définition de types .d.ts.

5.2. Programme principal

A partir d'un tel module animal.js, le code de l'application app.ts est strictement identique à celui du chapitre précédent.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
// app.ts 
  
import * as Animal from "animal"; // Module ES2015 
  
export function start() { 
    let garry = new Animal.Snail('Garry'); 
    let simbad = new Animal.Lion('Simbad', Animal.Sex.MALE); 
  
    console.log(garry.name); 
    console.log(simbad.name, simbad.sex); 
}

Notons cependant que le module animal qui est mentionné dans la clause import, ne correspond plus au fichier animal.ts comme au chapitre précédent, puisque celui-ci n'existe pas ici, mais au fichier animal.d.ts qui fait office de contrat. Nous assurons à TypeScript qu'un module tel que décrit dans animal.d.ts sera présent lors de l'exécution de l'application.

5.3. Résultat de la compilation

Lorsque nous lançons la compilation, aucune erreur ne se produit, mais nous pouvons constater un souci. Aucun fichier animal.js dans le répertoire cible build/. En effet, par défaut, le compilateur TypeScript ne traite pas les fichiers JavaScript en entrée. Il faut lui indiquer à l'aide de l'option de compilation --allowJs que nous pouvons également inclure dans le fichier projet tsconfig.json.

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// tsconfig.json 
  
{ 
    "compilerOptions": { 
        "noImplicitAny": true, 
        "strictNullChecks": true, 
        "target": "es5", 
        "module": "amd", 
        "outDir": "./build", 
        "allowJs": true 
    }, 
    "exclude" : [ 
        "./build", 
        "./node_modules" 
    ] 
}

Une fois cette modification apportée au fichier tsconfig.json, le résultat de la compilation devrait donner l'arborescence suivante où on trouve bien un nouveau fichier animal.js en sortie dans le répertoire build/ :

Projet/
build/
animal.js
app.js
config.js
node_modules/
@types/ ...
requirejs/
require.js
...
script/
animal.d.ts
animal.js
app.ts
config.ts
index.html
tsconfig.json


Nous venons ainsi d'importer un module nativement écrit en JavaScript, dans une application TypeScript. A noter que la démarche aurait été la même si le fichier animal.js en entrée avait été écrit en JavaScript ES5 avec une enveloppe AMD déjà présente. Pour s'en assurer, il suffit de remplacer le fichier script/animal.js par le fichier présent dans build/animal.js et constater que la compilation fonctionne parfaitement.

A partir du moment que nous fournissons à TypeScript un module ayant une forme connu (AMD, CommonJS, UMD, ES2015) et qu'on lui associe un fichier de définition des types .d.ts convenable, le processus décrit dans ce chapitre s'applique à l'identique. A noter cependant qu'on ne peut pas avoir un module au format AMD en entrée et générer en sortie un module au format CommonJS. Ça ce n'est pas possible. Seul le format de module ES2015 (qui est le format de base TypeScript depuis la version 1.5) peut se convertir dans les autres formats de module.

Maintenant, examinons un troisième cas d'importation, celui où le module en entrée, n'est en fait pas vraiment un module, mais un script "à plat", sans enveloppe prédéfinie.

6. Importation d'un fichier JavaScript à plat

6.1. Scripts à plat

Dans cet exemple, nous n'aurons non pas un mais deux fichiers à importer. Même s'ils seront dénommés modules par la suite, il faut bien noter qu'ils n'en ont pas la forme. Il s'agit juste de deux scripts à plat, sans enveloppe AMD ou autre.

Code javascript : Sélectionner tout
1
2
3
4
5
6
7
// mineral.js 
  
class Mineral { 
    constructor(name) { 
        this.name = name; 
    } 
}
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
// metal.js 
  
class Metal extends Mineral { 
    constructor(name, meltingPoint) { 
        super(name); 
        this.meltingPoint = meltingPoint; 
    } 
}

Comme pour tout fichier JavaScript en entrée, il convient de leur adjoindre deux fichiers de définition de types.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
// mineral.d.ts 
  
declare abstract class Mineral { 
    name: string; 
    constructor(name: string); 
} 
  
export = Mineral;
Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
// metal.d.ts 
  
declare class Metal extends Mineral { 
    meltingPoint: number; 
    constructor(name: string, meltingPoint: number); 
} 
  
export = Metal;

Notons dans ces fichiers de définition de types la présence à la fin de l'instruction export suivi du symbole =. Cela signifie que dans le fichier mineral.d.ts (resp. metal.d.ts), Mineral (resp. Metal) est l'objet exporté par défaut. Ceci est la syntaxe TypeScript avant la prise en charge de la syntaxe ES2015 pour les modules (TypeScript 1.6).

Nous ne pouvons malheureusement pas faire appel à la syntaxe ES2015 avec le mot-clé default car il faut pour cela que l'enveloppe du module soit au format ES2015, ce qui n'est évidemment pas le cas ici puisque derrière ces fichiers de définition de types, nous avons des fichiers JavaScript à plat.

6.2. Fichier de configuration RequireJS

Dans la mesure où nous n'avons pas affaire à des modules AMD, nous devons expliciter à RequireJS les dépendances et les objets à exposer.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// config.ts 
  
require.config({ 
    shim: { 
        'mineral': { 
            exports: 'Mineral' 
        }, 
        'metal': { 
            deps: ['mineral'], 
            exports: 'Metal' 
        } 
    } 
}); 
  
require( 
    ['app'], 
  
    function (app: any) { 
        app.start(); 
    }, 
  
    function (err: any) { 
        console.error('ERROR: ', err.requireType); 
        console.error('MODULES: ', err.requireModules); 
    } 
);

Par rapport aux chapitres précédents, nous avons ajouté un appel à la fonction require.config() qui sert à paramétrer RequireJS. Le paramètre shim de l'objet passé en argument précise le comportement du chargeur de modules pour les scripts qui ne respecte pas le format AMD. Et c'est justement notre cas. Nous indiquons à RequireJS que mineral.js (resp. metal.js) doit exporter (exports) dans le contexte global window, l'objet Mineral (resp. Metal) puisqu'en JavaScript, une classe est une fonction qui est un objet. Nous indiquons également que le module metal.js dépend de mineral.js via le paramètre deps.

Grâce à ces paramétrages, RequireJS chargera les scripts mineral.js et metal.js comme si c'étaient des modules AMD.

6.3. Programme principal

Nous pouvons donc désormais importer metal.js dans notre application via une clause import qui, comme pour la clause export vue précédemment, a ici une syntaxe différente de la norme ES2015 pour les modules.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
// app.ts 
  
import Metal = require('metal'); // JavaScript à plat 
  
export function start() { 
    let gold = new Metal('Gold', 1064); 
  
    console.log(gold.name, gold.meltingPoint); 
}

6.4. Résultat de la compilation

La compilation transpile correctement en ES5 les fichiers JavaScript mineral.js et metal.js. Et bien que ceux-ci ne possèdent pas d'enveloppe AMD, la page Web se lance sans problème, grâce au fichier config.js.

Projet/
build/
app.js
config.js
metal.js
mineral.js
node_modules/
@types/ ...
requirejs/
require.js
...
script/
app.ts
config.ts
metal.d.ts
metal.js
mineral.d.ts
mineral.js
index.html
tsconfig.json


7. Importation de bibliothèques JavaScript courantes

Maintenant que nous avons vu les différentes manières pour importer des modules en TypeScript, nous pouvons appliquer ces méthodes pour importer des bibliothèques existantes et largement diffusées dans la communauté des développeurs Web. Dans cet exemple, nous allons montrer comment importer les bibliothèques jQuery et underscore.

7.1. Installation des bibliothèques

Pour installer jQuery et son fichier de définition de types, voici les lignes de commande à exécuter à la racine du projet :
npm install jquery
npm install @types/jquery

Pour installer underscore et son fichier de définition de types, voici les lignes de commandes à exécuter à la racine du projet :
npm install underscore
npm install @types/underscore

Suite à cela, tout devrait avoir été installé dans des sous-répertoires de node_modules/.

7.2. Programme principal

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
// app.ts 
  
import $ = require("jquery"); 
import _ = require("underscore"); 
  
export function start() { 
    $("h1").hide(); 
    console.log(_.isEmpty({})); 
}

Dans la mesure où les deux modules sont au format AMD, et non au format ES2015, nous devons utiliser l'ancienne syntaxe de TypeScript pour importer ces modules.

En l'état, le compilateur devrait nous indiquer qu'il ne trouve pas le module underscore, alors qu'étrangement il semble avoir localisé le module jquery. Cette différence de traitement alors que les deux modules se trouvent à des endroits similaires, s'expliquent par le contenu de leur fichier de définition de types respectif, index.d.ts. Le fichier de définition de types du module jquery déclare bien l'existence d'un module nommé jquery.

Code typescript : Sélectionner tout
1
2
3
4
5
// node_modules/@types/jquery/index.d.ts 
  
declare module "jquery" { 
    export = $; 
}

Contrairement au fichier de définition de types du module underscore, node_modules/@types/underscore/index.d.ts, qui se contente d'exporter son espace de nommage sans l'emballer dans une déclaration de module. A noter qu'il ne faut pas se laisser abuser par le declare module _ juste sous l'export. Il s'agit de l'ancienne syntaxe pour déclarer un espace de nommage qui employait également le mot-clé module, mais qui désormais utilise namespace, ce dernier prêtant beaucoup moins à confusion.

7.3. Fichier projet

En tout cas, même si le module jquery est localisé, nous devons indiquer à TypeScript où se trouve underscore. La solution la plus évidente serait d'ajouter dans l'appel à require, un chemin d'accès devant le nom du module. Cependant, pour des raisons de maintenabilité, il est préférable de passer par une option du compilateur, --moduleResolution, déjà évoquée plus haut, et qui avec la valeur node, permet au compilateur de rechercher les modules dans le répertoire node_modules/.

Cette option est également disponible dans le fichier projet tsconfig.json comme ci-dessous :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// tsconfig.json 
 
{ 
    "compilerOptions": { 
        "noImplicitAny": true, 
        "strictNullChecks": true, 
        "target": "es5", 
        "module": "amd", 
        "moduleResolution": "node", 
        "allowJs" : true, 
        "outDir": "./build" 
    }, 
    "exclude" : [ 
        "./build", 
        "./node_modules" 
    ] 
}
Avec ce nouveau paramétrage, le compilateur devrait cette fois-ci localiser le module underscore (et le module jquery) comme il se doit.

7.4. Fichier de configuration RequireJS

Même si tout compile correctement, nous devons néanmoins penser à préciser le chemin d'accès à ces deux modules dans le fichier de configuration de RequireJS afin de permettre leur chargement au moment de l'exécution.

Code typescript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// config.ts 
  
require.config({ 
    paths: { 
        jquery: '../node_modules/jquery/dist/jquery', 
        underscore: '../node_modules/underscore/underscore' 
    } 
}); 
  
require( 
    ['app'], 
  
    function (app: any) { 
        app.start(); 
    }, 
  
    function (err: any) { 
        console.error('ERROR: ', err.requireType); 
        console.error('MODULES: ', err.requireModules); 
    } 
);

7.5. Résultat de la compilation

Projet/
build/
app.js
config.js
node_modules/
@types/
jquery/ ...
requirejs/ ...
underscore/ ...
jquery/ ...
requirejs/
require.js
...
underscore/ ...
script/
app.ts
config.ts
index.html
tsconfig.json


8. Conclusion

Dans cet article, nous avons pu voir différents cas d'importation de modules externes, écrits en TypeScript ou en JavaScript, avec des formes diverses. Cependant, tous les cas possibles n'ont évidemment pas été couverts, mais devrait donner une bonne base pour extrapoler l'importation de modules dans des situations plus ou moins proches.

En espérant que cet article vous a été utile, n'hésitez pas à le partager.


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :
Offres d'emploi IT
Développeur confirmé (H/F)
IT-CE - Provence Alpes Côte d'Azur - Aix-en-Provence (13100)
Ingénieur de développement (H/F)
IT-CE - Nord Pas-de-Calais - Lille (59000)
Ingénieur développeur java / web (H/F)
PROTECTIC - Ile de France - Puteaux (92800)

Voir plus d'offres Voir la carte des offres IT
Responsables bénévoles de la rubrique TypeScript : Xavier Lecomte - Lana Bauer -