JavaScript

TypeScript : Comment mettre en confiance les développeurs .NET vis-à-vis de JavaScript

Shayne Boyer

 

Il ne fait aucun doute que vous devez fournir un investissement considérable dans le Microsoft .NET Framework et cette plateforme est véritablement riche en outils disponibles. Si vous combinez vos connaissances en C# ou Visual Basic .NET avec XAML, le marché pour vos compétences existantes peut sembler presque illimité. À l'heure actuelle, cependant, vous devez envisager un langage déjà établi depuis quelque temps et dont l'utilisation s'est vraiment étendue au cours de ces dernières années avec la diffusion plus large de la plateforme d'application. Je fais bien évidemment référence au JavaScript. La croissance et la capacité des applications JavaScript sont phénoménales. Node.js, une plateforme complète qui permet de développer des applications JavaScript évolutives, est devenue extrêmement populaire. Elle peut d'ailleurs être déployée sur Windows Azure. En outre, JavaScript peut être utilisé avec HTML5 pour le développement de jeux, d'applications mobiles, voire d'applications du Windows Store.

En tant que développeur .NET, vous ne pouvez pas ignorer les capacités de JavaScript, pas plus que sa présence étendue sur le marché. Lorsque je tiens ce discours auprès de mes collègues, je les entends souvent se plaindre au sujet des difficultés d'utilisation de JavaScript, de l'absence d'un typage fort et de structures de classes. Je m'oppose à ces arguments en répondant que JavaScript est un langage fonctionnel et qu'il existe des modèles permettant d'exécuter ce que vous souhaitez.

C'est là que TypeScript entre en jeu. TypeScript n'est pas un nouveau langage. Il s'agit d'un sur-ensemble de JavaScript, un sur-ensemble typé et puissant, ce qui implique que tout JavaScript est du TypeScript valide et tout ce qui est produit par le compilateur est du JavaScript. TypeScript est un projet open source et toutes les informations qui sont associées à ce projet sont disponibles sur typescriptlang.org. À l'heure où cet article a été rédigé, TypeScript était en version préliminaire 0.8.1.

Dans cet article, je vais présenter les concepts de base de TypeScript sous forme de classes, modules et types afin d'illustrer comment un développeur .NET peut être plus à l'aise avec un projet JavaScript.

Classes

Si vous utilisez des langages tels que C# ou Visual Basic .NET, vous connaissez déjà le concept de classes. En JavaScript, les classes et l'héritage sont réalisés par l'intermédiaire de modèles tels que les fermetures et les prototypes. TypeScript inclut la syntaxe de type classique à laquelle vous êtes habitué et le compilateur produit le JavaScript qui met en œuvre l'intention. Prenons l'exemple de l'extrait JavaScript suivant :

var car;
car.wheels = 4;
car.doors = 4;

Il semble simple et direct. Toutefois, les développeurs .NET hésitent à se plonger véritablement dans JavaScript en raison de son approche vague de la définition d'objets. L'objet car peut se voir ajouter des propriétés ultérieurement sans exécution et sans que vous sachiez quel type de données est représenté par chaque propriété, et donc lancer des exceptions au cours de l'exécution. En quoi la définition du modèle de classe TypeScript change-t-elle cela et comment pouvons-nous hériter de car et l'étendre ? Prenons l'exemple de la figure 1.

Figure 1 Objets dans TypeScript et JavaScript

TypeScript JavaScript
class Auto{  wheels;  doors;}var car = new Auto();car.wheels = 2;car.doors = 4; var Auto = (function () {  function Auto() { }  return Auto;})();var car = new Auto();car.wheels = 2;car.doors = 4;

La colonne de gauche contient un objet de classe bien défini, nommé car, avec les propriétés wheels et doors. Dans celle de droite, le JavaScript produit par le compilateur TypeScript est presque identique. La seule différence est la variable Auto.

Dans l'éditeur TypeScript, vous ne pouvez pas ajouter de propriété sans recevoir un avertissement. Vous ne pouvez pas vous contenter d'utiliser une instruction telle que car.trunk = 1. Le compilateur se plaindrait et indiquerait qu'il n'existe aucune propriété trunk pour Auto, ce qui est une aubaine pour toute personne qui a déjà dû rechercher cette erreur en raison de la souplesse de JavaScript ou, selon votre point de vue, de sa paresse.

Les constructeurs, bien qu'ils soient disponibles dans JavaScript, sont à nouveau améliorés par les outils TypeScript en appliquant la création de l'objet au moment de la compilation et en interdisant la création de l'objet si les éléments et types appropriés n'ont pas été passés dans l'appel.

Non seulement vous pouvez ajouter le constructeur à la classe, mais vous pouvez également rendre les paramètres facultatifs, définir une valeur par défaut ou créer un raccourci pour la déclaration de propriété. Examinons les trois exemples qui illustrent la puissance potentielle de TypeScript.

La figure 2 représente le premier exemple, un constructeur simple dans lequel la classe est initialisée en passant les paramètres wheels et doors (indiqués ici par w et d). Le JavaScript produit (sur la droite) est presque équivalent, mais à mesure que la dynamique et les besoins de votre application se développeront, cela ne sera pas toujours le cas.

Figure 2 Un constructeur simple

TypeScript JavaScript
class Auto{  wheels;  doors;  constructor(w, d){    this.wheels = w;    this.doors = d;  }}var car = new Auto(2, 4); var Auto = (function () {  function Auto(w, d) {    this.wheels = w;    this.doors = d;  }  return Auto;})();var car = new Auto(2, 4);

 

À la figure 3, j'ai modifié le code de la figure 2 en attribuant par défaut la valeur 4 au paramètre wheels (w) et en rendant facultatif le paramètre doors (d) grâce à l'insertion d'un point d'interrogation à sa droite. Vous remarquerez que, comme dans l'exemple précédent, le modèle de définition de la propriété d'instance sur les arguments est une pratique courante qui utilise le mot clé « this ».

Figure 3 Un constructeur simple, modifié

TypeScript JavaScript
class Auto{  wheels;  doors;  constructor(w = 4, d?){    this.wheels = w;    this.doors = d;  }}var car = new Auto(); var Auto = (function () {  function Auto(w, d) {    this.wheels = w;    this.doors = d;  }  return Auto;})();var car = new Auto(4, 2);

Voici une fonctionnalité que j'aimerais voir dans les langages .NET : pouvoir ajouter simplement le mot clé Public devant le nom du paramètre dans le constructeur pour déclarer la propriété sur la classe. Le mot clé Private est disponible et effectue la même déclaration automatique, mais il masque la propriété de la classe.

Les valeurs par défaut, les paramètres facultatifs et les annotations de type sont étendus avec la fonctionnalité de déclaration automatique de propriété TypeScript, ce qui en fait un raccourci intéressant et vous permet d'accroître votre productivité. Comparez avec le script de la figure 4 et vous pourrez voir les différences de complexité qui commencent à apparaître.

Figure 4 La fonctionnalité de déclaration automatique

TypeScript JavaScript
class Auto{  constructor(public wheels = 4,    public doors?){  }}var car = new Auto();car.doors = 2; var Auto = (function () {  function Auto(wheels, doors) {    if (typeof wheels ===      "undefined") {      wheels = 4; }    this.wheels = wheels;    this.doors = doors;  }  return Auto;})();var car = new Auto();car.doors = 2;

 

Les classes en TypeScript apportent également l'héritage. Toujours dans le cadre de l'exemple Auto, vous pouvez créer une classe Motorcycle qui étend la classe initiale. À la figure 5, j'ajoute également des fonctions drive et stop à la classe de base. L'ajout de la classe Motorcycle, qui hérite de la classe Auto et définit les propriétés appropriées pour doors et wheels, est effectué avec quelques lignes de code en TypeScript.

Figure 5 Ajout de la classe Motorcycle

class Auto{
  constructor(public mph = 0,
    public wheels = 4,
    public doors?){
  }
  drive(speed){
    this.mph += speed;
  }
  stop(){
    this.mph = 0;
  }
}
class Motorcycle extends Auto
{
  doors = 0;
  wheels = 2;
}
var bike = new Motorcycle();

Il est important de mentionner ici que, en haut du JavaScript produit par le compilateur, vous verrez une petite fonction nommée « ___extends », comme illustré à la figure 6. Il s'agit du seul code injecté dans le JavaScript obtenu. Cette classe d'assistance apporte son aide dans le cadre de la fonctionnalité d'héritage. Par ailleurs, cette fonction d'assistance a très exactement la même signature quelle que soit la source. Par conséquent, si vous organisez votre JavaScript dans plusieurs fichiers et que vous avez recours à un utilitaire tel que SquishIt ou Web Essentials pour combiner vos scripts, il est possible que vous obteniez une erreur selon la façon dont cet utilitaire rectifie les fonctions dupliquées.

Figure 6 Le JavaScript produit par le compilateur

var __extends = this.__extends || function (d, b) {
  function __() { this.constructor = d; }
  __.prototype = b.prototype;
  d.prototype = new __();
}
var Auto = (function () {
  function Auto(mph, wheels, doors) {
    if (typeof mph === "undefined") { mph = 0; }
    if (typeof wheels === "undefined") { wheels = 4; }
    this.mph = mph;
    this.wheels = wheels;
    this.doors = doors;
  }
  Auto.prototype.drive = function (speed) {
    this.mph += speed;
  };
  Auto.prototype.stop = function () {
    this.mph = 0;
  };
  return Auto;
})();
var Motorcycle = (function (_super) {
  __extends(Motorcycle, _super);
  function Motorcycle() {
    _super.apply(this, arguments);
    this.doors = 0;
    this.wheels = 2;
  }
  return Motorcycle;
})(Auto);
var bike = new Motorcycle();

Modules

En TypeScript, les modules sont similaires aux espaces de noms dans le .NET Framework. Ils sont parfaits pour organiser le code et encapsuler les règles et processus métier qui seraient impossibles sans cette fonctionnalité (JavaScript ne dispose pas de solution intégrée pour fournir cette fonction). Le modèle de module, ou l'attribution dynamique d'espaces de noms, comme dans JQuery, est le modèle le plus courant pour les espaces de noms en JavaScript. Les modules TypeScript simplifient la syntaxe et produisent le même effet. Dans l'exemple Auto, vous pouvez encapsuler le code dans un module et exposer uniquement la classe Motorcycle, comme indiqué à la figure 7.

Le module Example encapsule la classe de base et la classe Motorcycle est exposée en la faisant précéder du mot clé export. Cela permet la création d'une instance de Motorcycle et l'utilisation de toutes ses méthodes, mais la classe de base Auto est masquée.

Figure 7 Encapsulation de la classe Auto dans un module

module Example {
  class Auto{
    constructor(public mph : number = 0,
      public wheels = 4,
      public doors?){
      }
      drive(speed){
      this.mph += speed;
      }
      stop(){
      this.mph = 0;
      }
  }
  export class Motorcycle extends Auto
  {
    doors = 0;
    wheels = 2;
  }
}
var bike = new Example.Motorcycle();

Autre avantage des modules, vous pouvez les fusionner. Si vous créez un autre module également nommé Example, TypeScript suppose que le code du premier module et celui du nouveau module sont tous les deux accessibles via des instructions Example, comme dans les espaces de noms.

Les modules facilitent la maintenance et l'organisation de votre code. Grâce à eux, les équipes de développement peuvent plus aisément maintenir des applications de grande envergure.

Types

L'absence de sécurité des types est l'une des plaintes les plus fréquentes parmi les développeurs qui ne travaillent pas avec JavaScript au quotidien. La sécurité des types est cependant disponible en TypeScript (c'est pourquoi elle est nommée TypeScript) et elle va au-delà de la déclaration d'une variable comme chaîne ou valeur booléenne.

En JavaScript, il est tout à fait acceptable d'attribuer foo à x, puis d'attribuer 11 à x ultérieurement dans le code, mais vous risquez de devenir fou en tentant de comprendre pourquoi vous obtenez l'omniprésent NaN lors de l'exécution.

La fonctionnalité de sécurité des types est l'un des plus grands avantages de TypeScript et il existe quatre types inhérents : string, number, bool et any. La figure 8 montre la syntaxe de déclaration du type de variable et l'IntelliSense fourni par le compilateur une fois qu'il sait quelles actions vous pouvez effectuer en fonction du type.

An Example of TypeScript IntelliSense
Figure 8 Un exemple d'IntelliSense TypeScript

Outre le fait qu'il autorise le typage d'une variable ou d'une fonction, TypeScript a la capacité d'inférer les types. Vous pouvez créer une fonction qui retourne simplement une chaîne. Sachant cela, le compilateur et les outils fournissent l'inférence de type et montrent les opérations susceptibles d'être effectuées au moment du retour, comme vous pouvez le voir à la figure 9.

An Example of Type Inference
Figure 9 Un exemple d'inférence de type

Ici, vous avez l'avantage de voir que le retour est une chaîne sans avoir besoin de jouer aux devinettes. L'inférence de type est d'une grande aide lorsqu'il s'agit d'utiliser d'autres bibliothèques référencées par les développeurs dans leur code, par exemple JQuery ou le modèle DOM (Document Object Model).

Les annotations vous permettent également de bénéficier du système de types. Rappelez-vous que la classe Auto initiale a été déclarée avec uniquement wheels and doors. Maintenant, grâce aux annotations, nous pouvons nous assurer que les types appropriés sont définis lors de la création de l'instance d'Auto dans car :

class Auto{
  wheels : number;
  doors : number;
}
var car = new Auto();
car.doors = 4;
car.wheels = 4;

Toutefois, dans le JavaScript qui est produit, les annotations sont compilées et vous n'avez donc pas besoin de vous préoccuper des ajouts et dépendances supplémentaires. Là encore, vous bénéficiez d'un typage fort et également de l'élimination des erreurs simples qui sont généralement trouvées lors de l'exécution.

Les interfaces fournissent un autre exemple de la sécurité de types offerte dans TypeScript. Elles vous permettent de définir la forme d'un objet. À la figure 10, une nouvelle méthode nommée travel a été ajoutée à la classe Auto et elle accepte un paramètre de type Trip.

Figure 10 L'interface Trip

interface Trip{
  destination : string;
  when: any;
}
class Auto{
  wheels : number;
  doors : number;
  travel(t : Trip) {
  //..
  }
}
var car = new Auto();
car.doors = 4;
car.wheels = 4;
car.travel({destination: "anywhere", when: "now"});

Si vous tentez d'appeler la méthode travel avec une structure autre que celle qui est correcte, le compilateur au moment de la conception vous donne une erreur. En comparaison, si vous avez entré ce code en JavaScript, par exemple dans un fichier .js, il y a de grandes chances pour que vous ne trouviez pas une erreur de ce type avant d'avoir exécuté l'application.

La figure 11 montre en quoi l'utilisation des annotations de type aide grandement non seulement le développeur initial, mais également le développeur suivant chargé de maintenir la source.

Annotations Assist in Maintaining Your Code
Figure 11 Les annotations facilitent la gestion du code

Bibliothèques et code existants

Qu'en est-il du code JavaScript existant ou que se passe-t-il si vous aimez utiliser un fichier Node.js comme base de création ou bien des bibliothèques telles que toastr, Knockout ou JQuery ? Les fichiers de déclaration de TypeScript sont là pour vous aider. Tout d'abord, n'oubliez pas que tout JavaScript est du TypeScript valide. Par conséquent, si vous avez créé votre propre code, vous pouvez le copier dans le concepteur et le compilateur produira strictement le JavaScript correspondant. La meilleure option consiste à créer votre propre fichier de déclaration.

Pour les bibliothèques et infrastructures principales, une personne nommée Boris Yankov (twitter.com/borisyankov sur Twitter) a créé un référentiel intéressant sur GitHub (github.com/borisyankov/DefinitelyTyped). Ce référentiel contient des fichiers de déclaration pour certaines des bibliothèques JavaScript les plus populaires. C'est exactement ce qu'espérait l'équipe TypeScript. D'ailleurs, le fichier de déclaration Node.js a été créé par l'équipe TypeScript et il est disponible avec le code source.

Création d'un fichier de déclaration

Si vous ne parvenez pas à trouver le fichier de déclaration correspondant à votre bibliothèque, ou si vous utilisez votre propre code, vous devrez créer un fichier de déclaration. Pour cela, commencez par copier le JavaScript dans le côté TypeScript et par ajouter les définitions de type, puis utilisez l'outil de ligne de commande afin de générer le fichier de définition (*.d.ts) à référencer.

La figure 12 montre un script simple de calcul de la moyenne pondérée cumulative en JavaScript. J'ai copié ce script dans la partie gauche de l'éditeur, puis j'ai ajouté les annotations pour les types et j'ai finalement enregistré le fichier avec l'extension .ts.

Figure 12 Création d'un fichier de déclaration

TypeScript JavaScript
function gradeAverage(grades : string[]) {  var total = 0;  var g = null;  var i = -1;  for(i = 0; i < grades.length; i++) {      g = grades[i];      total += getPointEquiv(grades[i]);  }  var avg = total / grades.length;  return getLetterGrade(Math.round(avg));}function getPointEquiv(grade : string) {  var res;  switch(grade) {    case "A": {      res = 4;      break;    }    case "B": {      res = 3;      break;    }    case "C": {      res = 2;      break;    }    case "D": {      res = 1;      break;    }    case "F": {      res = 0;      break;    }  }  return res;}function getLetterGrade(score : number) {  if(score < 1) {    return "F";  }  if(score > 3) {    return "A";  }  if(score > 2 && score < 4) {    return "B";  }  if(score >= 1 && score <= 2) {    return "C";  }  if(score > 0 && score < 2) {    return "D";  }} function gradeAverage(grades){  var total = 0;  var g = null;  var i = -1;  for(i = 0; i < grades.length; i++) {      g = grades[i];      total += getPointEquiv(grades[i]);  }  var avg = total / grades.length;  return getLetterGrade(Math.round(avg));}function getPointEquiv(grade) {  var res;  switch(grade) {    case "A": {      res = 4;      break;    }    case "B": {      res = 3;      break;    }    case "C": {      res = 2;      break;    }    case "D": {      res = 1;      break;    }    case "F": {      res = 0;      break;    }  }  return res;}function getLetterGrade(score) {  if(score < 1) {    return "F";  }  if(score > 3) {    return "A";  }  if(score > 2 && score < 4) {    return "B";  }  if(score >= 1 && score <= 2) {    return "C";  }  if(score > 0 && score < 2) {    return "D";  }}

J'ouvre ensuite une invite de commande et j'utilise l'outil de ligne de commande TypeScript pour créer le fichier de définition et le JavaScript obtenu :

tsc c:\gradeAverage.ts –declarations

Le compilateur crée deux fichiers : gradeAverage.d.ts, le fichier de déclaration, et gradeAverage.js, le fichier JavaScript. Par la suite, lorsque des fichiers TypeScript auront besoin de la fonctionnalité gradeAverage, j'ajouterai simplement une référence en haut de l'éditeur, de la façon suivante :

/// <reference path="gradeAverage.d.ts">

Ensuite, tout le typage et tous les outils sont mis en surbrillance lors du référencement de cette bibliothèque. C'est également le cas des bibliothèques principales disponibles dans le référentiel GitHub DefinitelyTyped.

Le compilateur apporte également une fonctionnalité exceptionnelle dans les fichiers de déclaration, à savoir la capacité de parcourir automatiquement les références. Concrètement, cela signifie que si vous référencez un fichier de déclaration pour jQueryUI, qui référence à son tour jQuery, votre fichier TypeScript actuel aura l'avantage de la saisie automatique des instructions et verra les signatures et les types de fonction comme si vous aviez référencé jQuery directement. Vous pouvez également créer un seul fichier de déclaration, par exemple « myRef.d.ts », qui contient les références à toutes les bibliothèques que vous avez l'intention d'utiliser dans votre solution, puis faire une seule référence dans votre code TypeScript.

Windows 8 et TypeScript

Avec HTML5, un acteur de choix dans le développement des applications du Windows Store, les développeurs se demandent si TypeScript peut être utilisé avec ces types d'applications. Je ferai court et répondrai par l'affirmative, mais vous devez pour cela procéder à une configuration spécifique. À l'heure où cet article est rédigé, les outils disponibles via le programme d'installation de Visual Studio ou d'autres extensions n'ont pas encore totalement activé les modèles figurant parmi les applications du Windows Store JavaScript dans Visual Studio 2012.

Trois fichiers de déclaration principaux sont disponibles dans le code source sur typescript.codeplex.com : winjs.d.ts, winrt.d.ts and lib.d.ts. Le référencement de ces fichiers vous donnera accès aux bibliothèques JavaScript WinJS et WinRT utilisées dans cet environnement pour accéder à la caméra, aux ressources système, etc. Vous pouvez également ajouter des références à jQuery pour obtenir les fonctionnalités IntelliSense et de sécurité de types que j'ai mentionnées dans cet article.

La figure 13 est un exemple rapide de l'utilisation de ces bibliothèques pour accéder à la géolocalisation d'un utilisateur et remplir une classe Location. Le code crée ensuite une balise d'image HTML et ajoute une carte statique depuis l'API Bing Map.

Figure 13 Fichiers de déclaration pour Windows 8

/// <reference path="winjs.d.ts" />
/// <reference path="winrt.d.ts" />
/// <reference path="jquery.d.ts" />
module Data {
  class Location {
    longitude: any;
    latitude: any;
    url: string;
    retrieved: string;
  }
  var locator = new Windows.Devices.Geolocation.Geolocator();
  locator.getGeopositionAsync().then(function (pos) {
    var myLoc = new Location();
    myLoc.latitude = pos.coordinate.latitude;
    myLoc.longitude = pos.coordinate.longitude;
    myLoc.retrieved = Date.now.toString();
    myLoc.url = "http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/"
      + myLoc.latitude + "," + myLoc.longitude
      + "15?mapSize=500,500&pp=47.620495,-122.34931;21;AA&pp="
      + myLoc.latitude + "," + myLoc.longitude
      + ";;AB&pp=" + myLoc.latitude + "," + myLoc.longitude
      + ";22&key=BingMapsKey";
    var img = document.createElement("img");
    img.setAttribute("src", myLoc.url);
    img.setAttribute("style", "height:500px;width:500px;");
    var p = $("p");
    p.append(img);
  });
};

Pour résumer

TypeScript ajoute de petites fonctionnalités au développement JavaScript, mais celles-ci offrent des avantages importants aux développeurs .NET qui sont habitués à des fonctionnalités similaires dans les langages qu'ils utilisent pour le développement classique d'applications Windows.

TypeScript n'est pas un remède miracle et ce n'est pas son intention. Mais pour toute personne qui hésite à se lancer dans JavaScript, TypeScript est un langage exceptionnel qui peut vous faciliter la tâche.

Shayne Boyer est MVP Telerik, champion en matière de développement Nokia, MCP, intervenant de l'INETA et architecte de solutions à Orlando, en Floride. Il développe des solutions Microsoft depuis 15 ans. Au cours des 10 dernières années, il a travaillé sur des applications Web de grande envergure, en ce concentrant essentiellement sur la productivité et les performances. Pendant son temps libre, M. Boyer dirige le groupe d'utilisateurs d'Orlando Windows Phone et Windows 8 et il écrit sur son blog des articles sur les dernières technologies, à l'adresse tattoocoder.com.

Merci à l'expert technique suivant d'avoir relu cet article : Christopher Bennage