Introduction au langage TypeScript

Le JavaScript++ ?

yahiko

Cet article est une introduction au langage TypeScript, alternative intéressante au JavaScript de base.

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez.

- Titre : Introduction au langage TypeScript

- Auteur : yahiko

- Parution : 15 octobre 2014

- Public visé :

- Licence : Copyright ® 2014 yahiko. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Introduction

- 2. Typage

- 2-1. Variables

- 2-2. Fonctions

- 3. Classe

- 4. Interface

- 5. Fonction anonyme fléchée

- 6. Modularité

- 6-1. Référencement de fichiers

- 6-2. Module explicite

- 6-3. Importation de modules externes

- 7. Typage générique

- 8. Outils

- 9. TypeScript et les autres

- 9-1. Maturité

- 9-2. Code source

- 9-3. Correspondance JavaScript

- 9-4. Portabilité

- 9-5. Popularité

- 9-6. Récapitulatif

- 10. Conclusion

- 10-1. Remerciements

- 11. Annexe : conversion en JavaScript

- 11-1. CoffeeScript 1.8.0

- 11-2. Dart 1.7.2

- 11-3. TypeScript 1.1

1 - Introduction

TypeScript est un langage très récent (février 2012) qui a été conçu par Anders Hejlsberg, également concepteur du langage C#. Le but premier de TypeScript est de rendre plus facile et plus fiable l'écriture de code en JavaScript pour des applications de grande ampleur.

Cet article présentera de façon succincte les principaux ajouts de TypeScript par rapport à JavaScript comme le typage, les classes, et la modularité, sachant que pour ceux connaissant déjà JavaScript, l'apprentissage de TypeScript ne devrait être qu'une simple formalité.

Puis, après la brève mention des principaux outils de développement utilisables avec TypeScript, nous comparerons l'approche de TypeScript avec deux autres langages similaires, CoffeeScript et Dart, et tenterons de montrer en quoi l'approche de TypeScript semble la plus viable à l'avenir.

2 - Typage

2-1 - Variables

Le principal apport du langage TypeScript, celui qui justifie le nom même du langage, est la possibilité d'associer, facultativement, un type à une donnée.

Typage explicite de variables

var pi: number;
var message: string;
var flag: boolean;
var joker: any;

Dans l'exemple ci-dessus, quatre variables sont déclarées sans être initialisées à l'aide d'un type dont la signification est explicite.

- La variable pi a pour type number, un nombre entier ou flottant.

- La variable message a pour type string, une chaîne de caractères.

- La variable flag a pour type boolean, un booléen qui peut prendre la valeur true ou false.

- La variable joker a pour type any, qui est le type par défaut qu'attribue TypeScript à une variable globale s'il ne parvient pas à déterminer son type lors de sa déclaration.

Bien entendu, il est possible d'initialiser une variable au moment de sa déclaration comme on peut le voir ci-dessous :

Typage implicite de variables

var pi = 3.14; // number
var message = "Bonjour !"; // string
var flag = true; // boolean
var joker = null; // any

Lors de la première initialisation d'une variable, TypeScript en infère automatiquement le type sans qu'il soit nécessaire de le mentionner explicitement.

Ainsi, TypeScript, contrairement à JavaScript, peut être considéré comme un langage à typage statique.

2-2 - Fonctions

Il est courant qu'une fonction renvoie un certain résultat. Le langage TypeScript permet de préciser le type du résultat attendu lors de la déclaration de la fonction.

Par défaut et en l'absence d'instruction return, le type du résultat d'une fonction est void, c'est-à-dire aucun résultat.

Typage d'une fonction

function triple(n: number): number {
return 3 * n;
}

La fonction triple ci-dessus est déclarée comme prenant un paramètre de type number et renvoyant une valeur de type number.

3 - Classe

La notion de classe introduite dans TypeScript anticipe la prochaine évolution de JavaScript (ECMAScript 6).

Définition d'une classe

class Animal {
name: string;

constructor(name: string) {
this.name = name;
}

shout(): string {
return "...";
}
}

Comme on peut le voir dans l'exemple ci-dessus, une classe Animal y est définie d'une façon proche de la plupart des langages orientés objet.

La classe Animal possède ici un attribut (name), elle définit un constructeur (constructor) et une méthode (shout). Son instanciation se fait à l'aide de l'opérateur new comme ceci :

Instanciation d'une classe

var animal = new Animal("pokemon");

De pair avec la notion de classe, TypeScript implémente la notion d'héritage simple par l'utilisation du mot-clé extends.

L'extension de la classe Animal de l'exemple précédent pourrait se faire ainsi :

Héritage

class Lion extends Animal {
sex: string;

constructor(name: string, sex: string) {
super(name);

this.sex = sex;
}

shout(): string {
return "Rooooaarrr!"
}
}

Cette nouvelle classe Lion ajoute un nouvel attribut sex à la classe Animal et redéfinit la méthode shout.

Puisque toutes les classes définies dans TypeScript sont considérées comme de nouveaux types, la classe Lion est du type Lion, et en vertu de l'héritage est aussi du type Animal.

En remarque, malgré cet apport orienté objet à la syntaxe initiale de JavaScript, il faut avoir conscience que les limitations intrinsèques de JavaScript se reflètent également dans TypeScript. Par exemple, la notion d'attribut privé (private) d'une classe qui existe dans la plupart des langages orientés objet, bien que syntaxiquement présente dans TypeScript, n'est pas véritablement restrictive dans la mesure où un attribut privé pourra malgré tout être utilisé en dehors de sa classe avec une approche dynamique.

4 - Interface

Une interface peut être vue tout d'abord comme une sorte de contrat minimum que doit respecter une structure de données en termes d'attributs et de méthodes. Cette structure de données peut être un objet {…} ou une classe.

Définition d'interfaces

interface I1 {
a: number;
}

interface I2 {
b: string;
}

L'interface I1 ci-dessus indique que la structure de données implémentant I1 doit avoir à minima un attribut nommé a de type number. L'interface I2 indique que la structure de données l'implémentant doit avoir à minima un attribut nommé b de type string.

Une interface est considérée comme un type à part entière, ce qui signifie qu'il est possible de définir une fonction qui prendrait un paramètre de type I1 comme l'exemple ci-dessous :

Interface en tant que type

function fct(x: I1) {
alert(x.a);
}

Cette fonction fct pouvant être appelée en passant en paramètre un objet ayant au moins un attribut nommé a :

Objet implémentant une interface

fct({ a: 20, z: -1 });

Un autre intérêt d'une interface, c'est qu'elle peut être considérée comme une classe n'ayant que des méthodes abstraites (ou pas de méthode du tout). Cela permet dans un langage comme TypeScript ne gérant que l'héritage simple, de se rapprocher du concept de l'héritage multiple, une classe pouvant implémenter (implements) plusieurs interfaces comme le montre l'exemple ci-dessous :

Classe implémentant deux interfaces

class C implements I1, I2 {
a: number;
b: string;

constructor(a: number, b: string) {
this.a = a;
this.b = b;
}
}

Une instance de cette classe C pourrait tout à fait être passée en paramètre de notre fonction fct définie plus haut.

var c = new C(15, "bonjour");
fct(c);

Enfin, comme nous venons de le voir, une interface pouvant être considérée comme une classe abstraite, il est possible de faire hériter une interface d'une autre à l'aide du mot-clé extends :

Héritage entre interfaces

interface I3 extends I2 {
c: boolean;
}

Dans l'exemple ci-dessus, la nouvelle interface I3 dérive de l'interface I2 définie plus haut en ajoutant un attribut c de type boolean.

5 - Fonction anonyme fléchée

Un autre apport notable de TypeScript à JavaScript est sa nouvelle manière de définir une fonction anonyme qui peut être dénommée ici notation « fléchée » (arrow function) en raison de l'utilisation du symbole =>.

Fonction anonyme fléchée

function alertMsg(msg: string) {
this.msg = msg;
this.timer = setInterval(
() => {
alert(this.msg);
},
500);
}

La fonction anonyme fléchée est définie dans cet exemple en premier paramètre de l'appel à setInterval. À gauche du symbole => doit être mentionnée la liste de paramètres entre parenthèses, et à droite du symbole =>, le corps de la fonction anonyme entre accolades.

L'avantage par rapport à l'actuelle norme de JavaScript (ECMAScript 5), est que la notation fléchée ne change pas la valeur de contexte du mot-clé this à l'intérieur de la fonction anonyme. Cela évite donc l'utilisation d'une variable intermédiaire servant à propager la valeur de this à l'intérieur de la fonction anonyme. Par exemple, la ligne de code précédente aurait pu être transcrite ainsi en JavaScript actuel :

Équivalent JS de la fonction anonyme fléchée

function alertMsg(msg: string) {
this.msg = msg;
var _this = this;
this.timer = setInterval(
function () {
alert(_this.msg);
},
500);
}

Cette nouvelle notation plus concise sera utile en particulier pour les fonctions callback appelées de façon asynchrone.

Il convient aussi de noter que la norme ECMAScript 6, si elle est approuvée, reprendra cette notation ; TypeScript permettant ainsi de se familiariser en avance avec ces fonctions anonymes fléchées.

6 - Modularité

La modularité en TypeScript est un sujet qui peut rapidement devenir complexe et nécessiterait un article en soi. Dans ce chapitre, nous nous contenterons de n'évoquer que les principales techniques.

6-1 - Référencement de fichiers

TypeScript introduit de nouvelles techniques de modularisation devenues nécessaires par le typage statique et par l'héritage de classes. Parmi ces techniques de modularisation, le référencement d'un fichier TypeScript externe est peut-être la plus simple.

Elle consiste à inclure de façon statique en tout début de fichier, un autre fichier source TypeScript contenant des déclarations nécessaires au compilateur afin qu'il en déduise entre autres les types et les héritages.

source.ts

/// <reference path="module.ts"/>

À noter que le référencement ressemble un peu aux #include des langages C/C++.

C'est cette approche qui est en général adoptée pour utiliser des bibliothèques JavaScript déjà existantes via les fichiers de déclaration .d.ts.

Sur certains environnements intégrés de développement comme Visual Studio, le référencement peut se faire implicitement, sans avoir besoin de mentionner le fichier contenant les dépendances de déclarations, même si un référencement explicite peut faciliter la documentation des dépendances entre fichiers sources.

Ce référencement permet très simplement de bénéficier à l'intérieur du fichier source appelant, partageant le même espace de nommage, des déclarations de variables, de fonctions ou de classes présentes dans le fichier référencé.

L'utilisation du référencement de fichiers se fait généralement pour les applications côté client où dans la page HTML il sera nécessaire soit de référencer via la balise <script> l'ensemble des fichiers JavaScript générés (c.f. index1.htm), soit de référencer le fichier issu de la concaténation éventuellement minifiée (.min.js) de ces fichiers JavaScript (c.f. index2.htm).

index1.htm

<!DOCTYPE html>
<html lang="fr">
<head>
<script src="module.js"></script>
<script src="source.js"></script>
</head>
<body>
...
</body>
</html>

Dans l'exemple ci-dessus, on suppose que les fichiers module.js et source.js sont le résultat de la compilation en JavaScript des fichiers module.ts et source.ts.

index2.htm

<!DOCTYPE html>
<html lang="fr">
<head>
<script src="source-module.min.js"></script>
</head>
<body>
...
</body>
</html>

Dans l'exemple ci-dessus, on suppose que le fichier source-module.min.js est la concaténation du fichier module.js suivi du fichier source.js.

Par souci de performance, la seconde solution consistant à fusionner les fichiers JavaScript résultants est en général privilégiée. C'est pourquoi les fichiers reliés par référencement (dans l'exemple module.ts) au fichier source principal (dans l'exemple source.ts) peuvent être qualifiés de modules internes, par opposition aux modules externes reliés par une autre méthode et que nous verrons un peu plus loin.

L'inconvénient de cette approche est que cela peut engendrer des conflits de nommage entre deux variables globales, fonctions ou classes pouvant porter la même appellation. Pour remédier à ce problème, TypeScript propose la notion de module explicite.

6-2 - Module explicite

En TypeScript chaque fichier source est implicitement un module.

Il est possible de déclarer explicitement un module, ce qui basiquement créera un espace de nommage permettant de spécifier les variables, fonctions, interfaces ou classes pouvant être importées dans un autre fichier, par exemple via référencement. Un module explicite est introduit par le mot-clé module.

module.ts

module M {
var temp = "bonjour";
export var id = 0;
export function fct() { … }
export interface I {
a: number;
}
export class C implements I {
a: number;
}
}

Dans l'exemple ci-dessus, le module M exporte, c'est-à-dire rend accessible, les éléments suivants :

- la variable id ;

- la fonction fct ;

- l'interface I ;

- et la classe C.

La variable temp est quant à elle interne à ce module M et ne pourra pas être utilisée en dehors de ce module.

Par exemple, dans ce même fichier source, l'instruction suivante utilisant la variable id est valide :

module.ts

alert(M.id);

alors que l'instruction ci-dessous utilisant la variable temp n'est pas valide :

module.ts

alert(M.temp); // erreur

6-3 - Importation de modules externes

Alors que précédemment la responsabilité de l'espace de nommage était au niveau du module appelé, forçant l'utilisateur à réutiliser l'espace de nommage du module concerné, il est possible de transférer la responsabilité de l'espace de nommage au niveau du fichier source appelant à l'aide des mot-clés import et require.

Reprenons l'exemple de la section précédente, mais cette fois sans créer d'espace de nommage via le mot-clé module :

module2.ts

export var id = 0;
export function fct() { … }
export interface I {
a: number;
}
export class C implements I {
a: number;
}

Ainsi défini, ce fichier module2.ts peut être importé dans un autre fichier, qu'on supposera pour l'exemple présent dans le même répertoire que module2.ts, de la manière suivante :

source2.ts

import m = require("./module2");

m.id++;

Ci-dessus, le symbole m ainsi défini via le mot-clé import est un nouvel espace de nommage englobant les éléments exportés du fichier module2.ts, sans avoir recours au référencement de fichiers via la syntaxe /// <reference>.

Cette technique de modularisation est surtout pratiquée pour des applications côté serveur (e.g. Node.js) où les différents fichiers n'ont pas vocation à être combinés en un seul fichier final JavaScript. C'est la raison pour laquelle on peut parler de modules externes dans ce cas.

7 - Typage générique

Le typage générique ajoute un niveau d'abstraction en rendant les types paramétrables, que ce soit dans une fonction, une classe ou une interface, sachant qu'au moment de l'appel effectif à cette fonction ou à cette classe, le type devra être explicitement défini.

Le typage générique est surtout utile en TypeScript lorsqu'il s'agit d'assurer la cohérence du typage entre différents éléments. Par exemple, si on considère un tableau de nombres, on pourrait souhaiter disposer d'une fonction de concaténation assurant de concaténer ce tableau de nombres avec un autre tableau de nombres et non avec un tableau de chaînes de caractères ou d'autres choses. Par contre, on pourrait souhaiter que cette même fonction de concaténation puisse s'appliquer sur deux tableaux de chaînes de caractères ou autres, sans avoir besoin de réécrire une nouvelle fonction pour chaque type traité. C'est à ce moment-là qu'intervient le typage générique.

Définition d'une fonction générique

function concatenate<T>(a1: T[], a2: T[]): T[] {
return a1.concat(a2);
}

resultNumbers = concatenate<number>([1, 2], [3, 4]); // [1, 2, 3, 4]
resultStrings = concatenate<string>(["a", "b"], ["c", "d"]); // ["a", "b", "c", "d"]
resultError1 = concatenate<number>([1, 2], ["a", "b"]); // erreur
resultError2 = concatenate<string>([1, 2], ["a", "b"]); // erreur
resultAny = concatenate<any>([1, 2], ["a", "b"]); // [1, 2, "a", "b"]

La syntaxe du typage générique est tout ce qu'il y a de classique pour les habitués de C++, C# ou de Java. Le type générique (dans l'exemple T) est déclaré entre chevrons <>, puis est utilisé comme un type ordinaire.

L'exemple montre la concaténation de deux tableaux de nombres (resultNumbers) et de deux tableaux de chaînes de caractères (resultStrings), et montre aussi qu'il n'est pas possible de concaténer un tableau de nombres avec un tableau de chaînes de caractères (resultError1 et resultError2) sous peine de lever une erreur.

Le typage générique est donc différent du type any puisqu'une fonction de concaténation entre deux tableaux de type any[] n'aurait pas levé d'erreur au niveau de la compilation comme l'indique le quatrième résultat de l'exemple (resultAny).

8 - Outils

Bien que TypeScript soit d'abord un compilateur installable via Node.js, pour être exploité à son plein potentiel, il est recommandé qu'il soit associé à un environnement intégré de développement comme Visual Studio, WebStorm ou éventuellement Eclipse.

Pour tester de petits morceaux de codes (snippets), il peut être intéressant d'utiliser le site TypeScript.io fonctionnant de façon similaire à JsFiddle.

Aussi, la page de test mise à disposition par Microsoft peut également être utile pour voir immédiatement la transcription en JavaScript d'un code TypeScript.

9 - TypeScript et les autres

Après cet aperçu concernant le « sucre syntaxique » de TypeScript, intéressons-nous au modèle de développement de ce langage comparativement à ses principaux concurrents que sont CoffeeScript (décembre 2009) et Dart (2011).

9-1 - Maturité

On constate tout d'abord que TypeScript est le plus jeune des trois, n'étant apparu qu'en 2012. Ce retard de maturité est encore visible dans la mesure où la version de production 1.0 n'a été disponible que cette année 2014. Cependant, force est de constater qu'à l'instar de Google avec Dart, Microsoft et son concepteur restent très impliqués dans l'évolution de TypeScript, ce qui ne peut que rassurer ses utilisateurs. Concernant TypeScript, il est raisonnable de tabler sur deux à trois évolutions majeures par an.

9-2 - Code source

Tout comme CoffeeScript et Dart, le code source du compilateur TypeScript est disponible en Open source (Licence Apache 2), sur le compte GitHub de Microsoft. Ce critère n'est donc pas discriminant pour évaluer la pertinence relative de ce langage sur les deux autres.

9-3 - Correspondance JavaScript

La conversion de TypeScript vers le JavaScript est assez directe et le sera encore davantage avec l'officialisation de la norme ECMAScript 6 puisque TypeScript est un sur-ensemble de JavaScript. Il est donc très aisé d'identifier le morceau de code TypeScript ayant produit un morceau de code JavaScript. Cette forte correspondance (voulue) entre TypeScript et JavaScript permet à TypeScript de bénéficier sans trop d'efforts aux innombrables bibliothèques, API ou frameworks écrits en JavaScript. C'est évidemment un gros avantage.

Ce n'est pas forcément le cas des deux autres langages. La syntaxe de CoffeeScript inspirée du langage Haskell peut parfois être absconse et en tous les cas significativement différente de JavaScript pouvant rendre le débogage, l'identification entre code source et le code traduit et l'importation de codes en JavaScript natifs non triviaux. Et cela est encore plus vrai pour Dart qui emploie une syntaxe différente à celle de JavaScript en s'inspirant davantage de Java et du C++. D'autant plus que les optimisations réalisées par Dart lors de la conversion en code JavaScript peuvent significativement changer la structure du code et l'ordre des instructions.

En fin de document, on trouvera en annexe le résultat de la conversion en JavaScript d'un programme simple rédigé en trois versions : CoffeeScript, Dart et TypeScript. Le lecteur se convaincra facilement que TypeScript est syntaxiquement le langage le plus proche de JavaScript, le code source étant très similaire au code cible, tandis que dans le cas de Dart avec son code cible interminable, on peut se poser la question de son lien avec JavaScript.

9-4 - Portabilité

Les trois langages permettent la conversion (ou transcompilation) en JavaScript et donc l'accès en théorie à tous les navigateurs du marché.

Cependant, pour bénéficier pleinement des performances promises par Dart, il est nécessaire que le navigateur dispose d'une machine virtuelle DartVM, ce qui n'est le cas actuellement que pour Chrome, évidemment. Dans la mesure où il est peu probable que Microsoft et Apple implémentent une DartVM dans Internet Explorer et dans Safari, il est raisonnable de considérer que ce qui est souvent vendu comme l'avantage décisif de Dart, ne soit finalement réservé qu'à une fraction des navigateurs. Par conséquent, Dart, à terme, devrait vraisemblablement rester marginal. Un peu comme Google Web Toolkit (2006) dont on retrouve beaucoup de caractéristiques avec Dart, et qui n'a pas vraiment eu de succès comme on peut le constater aujourd'hui.

C'est pourquoi JavaScript, en tant que langage natif de tous les navigateurs, devrait rester le seul standard pendant encore de nombreuses années.

Et l'approche de TypeScript se voulant la plus respectueuse des normes de JavaScript a de grandes chances d'être la bonne.

9-5 - Popularité

Bien que les classements de popularité soient toujours sujets à caution, et ne mentionnons même pas l'indice très douteux de Tiobe, c'est souvent sur ce point que se décide l'adoption d'un langage, ce suivisme entraînant un effet boule de neige dont chacun est libre de penser ce qu'il voudra. Mais c'est un fait et cela a son importance.

Si nous nous basons sur la plate-forme d'hébergement de codes source GitHub qui est très prisée des développeurs Web comme l'indique la position dominante de JavaScript sur les autres langages, nous devrions avoir une tendance objective relativement proche des usages dans ce domaine.

Langage

2012-2013

2013-2014

+/-

JavaScript

531 482

702 596

32 %

CoffeeScript

26 362

42 094

60 %

Dart

3 120

3 393

9 %

TypeScript

2 150

6 558

205 %

Évolution(1) annuelle (d'octobre à septembre) de la création de repositories sur GitHub

On peut constater que la croissance de Dart (9 %) reste assez stable et en deçà de ses concurrents, y compris JavaScript lui-même (32 %). CoffeeScript continue quant à lui de connaître une belle dynamique avec une croissance des créations de +60 %. TypeScript qui comptabilisait en 2012-2013 moins de créations de repositories que Dart, vient de le dépasser en 2013-2014 avec un nombre de créations deux fois plus fort et une croissance annuelle qui explose à +205 %.

Ceci est un indicateur laissant à penser que TypeScript a un grand potentiel d'adoption parmi la communauté des développeurs Web malgré sa jeunesse. On peut signaler à titre d'exemple que le framework graphique babylon.js a annoncé récemment la migration de son code de JavaScript vers TypeScript. Gageons qu'avec la récente mise à disposition d'une version de production et donc stable du compilateur TypeScript, d'autres migrations d'ampleur vers ce langage voient le jour dans les mois qui viennent.

9-6 - Récapitulatif

CoffeeScript

Dart

TypeScript

Maturité

5 ans

4 ans

3 ans

Code source

Open source

Open source

Open source

Correspondance

Moyenne

Faible

Forte

Portabilité

JavaScript

JavaScript sans DartVM

JavaScript

Popularité

Restreinte,
forte croissance

Confidentielle,
faible croissance

Confidentielle,
très forte croissance

10 - Conclusion

Cette présentation est évidemment loin d'être complète. Des mécanismes comme les mixins n'ont pas été abordés tout comme les possibilités offertes avec les fonctions concernant les paramètres et la surcharge. Pour se familiariser avec TypeScript, il peut être utile d'utiliser la page de test afin de voir instantanément la conversion d'un code TypeScript en un code JavaScript. Sinon, le guide d'utilisation et la spécification, malheureusement ni l'un ni l'autre n'étant traduit en français, restent indispensables pour approfondir le sujet.

En dépit de sa courte existence, TypeScript est un langage en pleine croissance tant sur le plan de ses spécifications que sur le plan de sa popularité. Il n'est pas parfait du fait de sa volonté de coller au plus près des normes de JavaScript, mais l'évolution de ces normes pour enrichir la syntaxe de JavaScript ainsi que des approches parallèles pour améliorer les performances comme asm.js permettent de penser que TypeScript pourrait à terme au mieux devenir lui-même un standard, au pire, inspirer les futures normes ECMAScript.

Toute proportion gardée, c'est une démarche similaire qu'avait eue à son époque Bjarne Stroustrup en concevant le C++ en tant que sur-ensemble du langage C. Et le succès jamais démenti du C++ depuis plusieurs décennies laisse à penser que TypeScript pourrait suivre le même chemin et devenir une sorte de JavaScript++.

10-1 - Remerciements

Je remercie Tarh_et vermine pour la relecture technique et leurs nombreuses suggestions ainsi que  Claude Leloup pour la relecture orthographique de cet article.

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez.

11 - Annexe : conversion en JavaScript

Les trois codes source proviennent du compte GitHub darthapo. Les codes cibles ont été réactualisés avec les derniers compilateurs en date.

11-1 - CoffeeScript 1.8.0

Code source CoffeeScript

class Simple

constructor: (@name = "default") ->

greet: (who) ->
"Greetings #{ who }, I'm #{ @name }!"

@main: ->
s= new Simple "Flynn"
console.log s.greet "Program"

Simple.main();

Code cible JavaScript

(function() {
var Simple;

Simple = (function() {
function Simple(name) {
this.name = name != null ? name : "default";
}

Simple.prototype.greet = function(who) {
return "Greetings " + who + ", I'm " + this.name + "!";
};

Simple.main = function() {
var s;
s = new Simple("Flynn");
return console.log(s.greet("Program"));
};

return Simple;

})();

Simple.main();

}).call(this);

11-2 - Dart 1.7.2

Code source Dart

class Simple {

String name;

Simple(this.name);

greet(who) => "Greetings $who, I'm $name!";
}

main() {
var s= new Simple('Flyn');

print( s.greet('Program'));
}

Code cible JavaScript

// Generated by dart2js, the Dart to JavaScript compiler version: 1.7.2.
// The code supports the following hooks:
// dartPrint(message):
//    if this function is defined it is called instead of the Dart [print]
//    method.
//
// dartMainRunner(main, args):
//    if this function is defined, the Dart [main] method will not be invoked
//    directly. Instead, a closure that will invoke [main], and its arguments
//    [args] is passed to [dartMainRunner].
if (typeof($dart_deferred_initializers) === "undefined") $dart_deferred_initializers = Object.create(null);
(function($) {
function dart() {
this.x = 0;
delete this.x;
}
var A = new dart;
var B = new dart;
var C = new dart;
var D = new dart;
var E = new dart;
var F = new dart;
var G = new dart;
var H = new dart;
var J = new dart;
var K = new dart;
var L = new dart;
var M = new dart;
var N = new dart;
var O = new dart;
var P = new dart;
var Q = new dart;
var R = new dart;
var S = new dart;
var T = new dart;
var U = new dart;
var V = new dart;
var W = new dart;
var X = new dart;
var Y = new dart;
var Z = new dart;
function Isolate() {}
init();

$ = Isolate.$isolateProperties;
(function(reflectionData) {
"use strict";
function map(x) {
x = Object.create(null);
x.x = 0;
delete x.x;
return x;
}
function processStatics(descriptor) {
for (var property in descriptor) {
if (!hasOwnProperty.call(descriptor, property))
continue;
if (property === "^")
continue;
var element = descriptor[property];
var firstChar = property.substring(0, 1);
var previousProperty;
if (firstChar === "+") {
mangledGlobalNames[previousProperty] = property.substring(1);
var flag = descriptor[property];
if (flag > 0)
descriptor[previousProperty].$reflectable = flag;
if (element && element.length)
init.typeInformation[previousProperty] = element;
} else if (firstChar === "@") {
property = property.substring(1);
$[property]["@"] = element;
} else if (firstChar === "*") {
globalObject[previousProperty].$defaultValues = element;
var optionalMethods = descriptor.$methodsWithOptionalArguments;
if (!optionalMethods) {
descriptor.$methodsWithOptionalArguments = optionalMethods = {};
}
optionalMethods[property] = previousProperty;
} else if (typeof element === "function") {
globalObject[previousProperty = property] = element;
functions.push(property);
init.globalFunctions[property] = element;
} else if (element.constructor === Array) {
addStubs(globalObject, element, property, true, descriptor, functions);
} else {
previousProperty = property;
var newDesc = {};
var previousProp;
for (var prop in element) {
if (!hasOwnProperty.call(element, prop))
continue;
firstChar = prop.substring(0, 1);
if (prop === "static") {
processStatics(init.statics[property] = element[prop]);
} else if (firstChar === "+") {
mangledNames[previousProp] = prop.substring(1);
var flag = element[prop];
if (flag > 0)
element[previousProp].$reflectable = flag;
} else if (firstChar === "@" && prop !== "@") {
newDesc[prop.substring(1)]["@"] = element[prop];
} else if (firstChar === "*") {
newDesc[previousProp].$defaultValues = element[prop];
var optionalMethods = newDesc.$methodsWithOptionalArguments;
if (!optionalMethods) {
newDesc.$methodsWithOptionalArguments = optionalMethods = {};
}
optionalMethods[prop] = previousProp;
} else {
var elem = element[prop];
if (prop !== "^" && elem != null && elem.constructor === Array && prop !== "<>") {
addStubs(newDesc, elem, prop, false, element, []);
} else {
newDesc[previousProp = prop] = elem;
}
}
}
$$[property] = [globalObject, newDesc];
classes.push(property);
}
}
}
function addStubs(descriptor, array, name, isStatic, originalDescriptor, functions) {
var f, funcs = [originalDescriptor[name] = descriptor[name] = f = array[0]];
f.$stubName = name;
functions.push(name);
for (var index = 0; index < array.length; index += 2) {
f = array[index + 1];
if (typeof f != "function")
break;
f.$stubName = array[index + 2];
funcs.push(f);
if (f.$stubName) {
originalDescriptor[f.$stubName] = descriptor[f.$stubName] = f;
functions.push(f.$stubName);
}
}
for (var i = 0; i < funcs.length; index++, i++) {
funcs[i].$callName = array[index + 1];
}
var getterStubName = array[++index];
array = array.slice(++index);
var requiredParameterInfo = array[0];
var requiredParameterCount = requiredParameterInfo >> 1;
var isAccessor = (requiredParameterInfo & 1) === 1;
var isSetter = requiredParameterInfo === 3;
var isGetter = requiredParameterInfo === 1;
var optionalParameterInfo = array[1];
var optionalParameterCount = optionalParameterInfo >> 1;
var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1;
var isIntercepted = requiredParameterCount + optionalParameterCount != funcs[0].length;
var functionTypeIndex = array[2];
var unmangledNameIndex = 2 * optionalParameterCount + requiredParameterCount + 3;
var isReflectable = array.length > unmangledNameIndex;
if (getterStubName) {
f = tearOff(funcs, array, isStatic, name, isIntercepted);
descriptor[name].$getter = f;
f.$getterStub = true;
if (isStatic)
init.globalFunctions[name] = f;
originalDescriptor[getterStubName] = descriptor[getterStubName] = f;
funcs.push(f);
if (getterStubName)
functions.push(getterStubName);
f.$stubName = getterStubName;
f.$callName = null;
if (isIntercepted)
init.interceptedNames[getterStubName] = true;
}
if (isReflectable) {
for (var i = 0; i < funcs.length; i++) {
funcs[i].$reflectable = 1;
funcs[i].$reflectionInfo = array;
}
var mangledNames = isStatic ? init.mangledGlobalNames : init.mangledNames;
var unmangledName = array[unmangledNameIndex];
var reflectionName = unmangledName;
if (getterStubName)
mangledNames[getterStubName] = reflectionName;
if (isSetter) {
reflectionName += "=";
} else if (!isGetter) {
reflectionName += ":" + requiredParameterCount + ":" + optionalParameterCount;
}
mangledNames[name] = reflectionName;
funcs[0].$reflectionName = reflectionName;
funcs[0].$metadataIndex = unmangledNameIndex + 1;
if (optionalParameterCount)
descriptor[unmangledName + "*"] = funcs[0];
}
}
function tearOffGetterNoCsp(funcs, reflectionInfo, name, isIntercepted) {
return isIntercepted ? new Function("funcs", "reflectionInfo", "name", "H", "c", "return function tearOff_" + name + functionCounter++ + "(x) {" + "if (c === null) c = H.closureFromTearOff(" + "this, funcs, reflectionInfo, false, [x], name);" + "return new c(this, funcs[0], x, name);" + "}")(funcs, reflectionInfo, name, H, null) : new Function("funcs", "reflectionInfo", "name", "H", "c", "return function tearOff_" + name + functionCounter++ + "() {" + "if (c === null) c = H.closureFromTearOff(" + "this, funcs, reflectionInfo, false, [], name);" + "return new c(this, funcs[0], null, name);" + "}")(funcs, reflectionInfo, name, H, null);
}
function tearOffGetterCsp(funcs, reflectionInfo, name, isIntercepted) {
var cache = null;
return isIntercepted ? function(x) {
if (cache === null)
cache = H.closureFromTearOff(this, funcs, reflectionInfo, false, [x], name);
return new cache(this, funcs[0], x, name);
} : function() {
if (cache === null)
cache = H.closureFromTearOff(this, funcs, reflectionInfo, false, [], name);
return new cache(this, funcs[0], null, name);
};
}
function tearOff(funcs, reflectionInfo, isStatic, name, isIntercepted) {
var cache;
return isStatic ? function() {
if (cache === void 0)
cache = H.closureFromTearOff(this, funcs, reflectionInfo, true, [], name).prototype;
return cache;
} : tearOffGetter(funcs, reflectionInfo, name, isIntercepted);
}
var functionCounter = 0;
var tearOffGetter = typeof dart_precompiled == "function" ? tearOffGetterCsp : tearOffGetterNoCsp;
if (!init.libraries)
init.libraries = [];
if (!init.mangledNames)
init.mangledNames = map();
if (!init.mangledGlobalNames)
init.mangledGlobalNames = map();
if (!init.statics)
init.statics = map();
if (!init.typeInformation)
init.typeInformation = map();
if (!init.globalFunctions)
init.globalFunctions = map();
if (!init.interceptedNames)
init.interceptedNames = map();
var libraries = init.libraries;
var mangledNames = init.mangledNames;
var mangledGlobalNames = init.mangledGlobalNames;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var length = reflectionData.length;
for (var i = 0; i < length; i++) {
var data = reflectionData[i];
var name = data[0];
var uri = data[1];
var metadata = data[2];
var globalObject = data[3];
var descriptor = data[4];
var isRoot = !!data[5];
var fields = descriptor && descriptor["^"];
if (fields instanceof Array)
fields = fields[0];
var classes = [];
var functions = [];
processStatics(descriptor);
libraries.push([name, uri, classes, functions, metadata, fields, isRoot, globalObject]);
}
})([
["dart2js._js_primitives", "dart:_js_primitives", , H, {
"^":
"",
printString: function(string) {
if (typeof dartPrint == "function") {
dartPrint(string);
return;
}
if (typeof console == "object" && typeof console.log != "undefined") {
console.log(string);
return;
}
if (typeof window == "object")
return;
if (typeof print == "function") {
print(string);
return;
}
throw "Unable to print message: " + String(string);
}
}],
["", "test.dart", , V, {
"^": "",
main: function() {
H.printString("Greetings Program, I'm Flyn!");
}
},
1],
]);
// Runtime type support
// getInterceptor methods
$.Closure_functionCounter = 0;
$.BoundClosure_selfFieldNameCache = null;
$.BoundClosure_receiverFieldNameCache = null;
$.printToZone = null;

init.metadata = [];
$ = null;
Isolate = Isolate.$finishIsolateConstructor(Isolate);
$ = new Isolate();
function convertToFastObject(properties) {
function MyClass() {
}
MyClass.prototype = properties;
new MyClass();
return properties;
}
;
A = convertToFastObject(A);
B = convertToFastObject(B);
C = convertToFastObject(C);
D = convertToFastObject(D);
E = convertToFastObject(E);
F = convertToFastObject(F);
G = convertToFastObject(G);
H = convertToFastObject(H);
J = convertToFastObject(J);
K = convertToFastObject(K);
L = convertToFastObject(L);
M = convertToFastObject(M);
N = convertToFastObject(N);
O = convertToFastObject(O);
P = convertToFastObject(P);
Q = convertToFastObject(Q);
R = convertToFastObject(R);
S = convertToFastObject(S);
T = convertToFastObject(T);
U = convertToFastObject(U);
V = convertToFastObject(V);
W = convertToFastObject(W);
X = convertToFastObject(X);
Y = convertToFastObject(Y);
Z = convertToFastObject(Z);
function init() {
Isolate.$isolateProperties = Object.create(null);
Isolate.$lazy = function(prototype, staticName, fieldName, getterName, lazyValue) {
if (!init.lazies)
init.lazies = Object.create(null);
init.lazies[fieldName] = getterName;
var sentinelUndefined = {};
var sentinelInProgress = {};
prototype[fieldName] = sentinelUndefined;
prototype[getterName] = function() {
var result = $[fieldName];
try {
if (result === sentinelUndefined) {
$[fieldName] = sentinelInProgress;
try {
result = $[fieldName] = lazyValue();
} finally {
if (result === sentinelUndefined)
$[fieldName] = null;
}
} else {
if (result === sentinelInProgress)
H.throwCyclicInit(staticName);
}
return result;
} finally {
$[getterName] = function() {
return this[fieldName];
};
}
};
};
Isolate.$finishIsolateConstructor = function(oldIsolate) {
var isolateProperties = oldIsolate.$isolateProperties;
function Isolate() {
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var staticName in isolateProperties)
if (hasOwnProperty.call(isolateProperties, staticName))
this[staticName] = isolateProperties[staticName];
var lazies = init.lazies;
for (var lazyInit in lazies) {
this[lazies[lazyInit]] = null;
}
function ForceEfficientMap() {
}
ForceEfficientMap.prototype = this;
new ForceEfficientMap();
for (var lazyInit in lazies) {
var lazyInitName = lazies[lazyInit];
this[lazyInitName] = isolateProperties[lazyInitName];
}
}
Isolate.prototype = oldIsolate.prototype;
Isolate.prototype.constructor = Isolate;
Isolate.$isolateProperties = isolateProperties;
return Isolate;
};
}
// BEGIN invoke [main].
;(function(callback) {
if (typeof document === "undefined") {
callback(null);
return;
}
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad(event) {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener("load", onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener("load", onLoad, false);
}
})(function(currentScript) {
init.currentScript = currentScript;
if (typeof dartMainRunner === "function") {
dartMainRunner(V.main, []);
} else {
V.main([]);
}
});
;
// END invoke [main].
})()

//# sourceMappingURL=out.js.map

11-3 - TypeScript 1.1

Code source TypeScript

class Simple {

constructor(public name: string) {}

greet(who: string) {
return "Greetings "+ who +", I'm "+ this.name +"!";
}

static main() {
var s = new Simple('Flynn');
console.log(s.greet("Program"));
}

}

Simple.main()

Code cible JavaScript

var Simple = (function () {
function Simple(name) {
this.name = name;
}
Simple.prototype.greet = function (who) {
return "Greetings " + who + ", I'm " + this.name + "!";
};

Simple.main = function () {
var s = new Simple('Flynn');
console.log(s.greet("Program"));
};
return Simple;
})();

Simple.main();

(1)Réalisée à partir d'une requête Google BigQuery sur le site githubarchive.