ES6 en détails est une série d’articles décrivant les nouvelles fonctionnalités ajoutées au langage de programmation JavaScript avec la sixième édition du standard ECMAScript (ES6 en abrégé).

La semaine dernière, je vous ai promis un changement de rythme. Après les itérateurs et les générateurs, nous allions nous attaquer à quelque chose de simple. Quelque chose qui ne vous retournerait pas le cerveau. Nous verrons à la fin de cet article comment je tiendrai cette promesse. Pour l’instant, commençons avec quelque chose de simple.

Les bases du backtick

ES6 introduit une nouvelle forme de syntaxe pour les littéraux de chaînes de caractères, appelée les gabarits de chaînes (template strings). Ceux-ci ressemblent à des chaînes ordinaires, si ce n’est qu’ils utilisent le caractère de l’accent grave (ou backtick) ` à la place des quotes ou doubles quotes habituelles ' ou “. Dans le cas le plus simple, ce sont juste des chaînes de caractères :

context.fillText(`Ceci n'est pas un string.`, x, y);

Mais il y a tout de même une raison pour laquelle ceux-ci sont appelés des « gabarits de chaînes de caractères » et pas simplement des « bonnes vieilles chaînes de caractères ennuyeuses qui ne font rien de spécial mais qui utilisent l’accent grave ». Les gabarits de chaînes de caractères font entrer l’interpolation de chaînes de caractères dans JavaScript. Autrement dit, ils représentent une façon élégante et agréable d’insérer des valeurs JavaScript au sein d’une chaîne.

Il existe des millions de manières pour utiliser cette fonctionnalité, mais celle qui me réchauffe le cœur est la suivante, utilisée pour représenter un message d’erreur :

function autoriser(utilisateur, action) {
  if (!utilisateur.aLePrivilege(action)) {
    throw new Error(
      `L'utilisateur ${utilisateur.nom} n'a pas le droit de ${action}.`);
  }
}

Dans cet exemple, ${utilisateur.nom} et ${action} sont appelés des substitutions de gabarits. Le moteur JavaScript va insérer les valeurs utilisateur.nom et action dans la chaîne résultante. Cela permettra de générer un message tel que « L’utilisateur jorendorff n’a pas le droit de pratiquer le hockey. » (ce qui s’avère exact, étant donné que je n’ai pas de licence de hockey).

Jusque là, on a simplement une syntaxe légèrement plus élégante que celle de l’opérateur +, avec les détails auxquels on s’attendrait :

  • Le code contenu dans une substitution de gabarit peut être n’importe quelle expression JavaScript : les appels de fonctions, les expressions arithmétiques, et autres sont donc autorisés (si vous le désirez, vous pouvez même embarquer un gabarit de chaîne dans un autre gabarit de chaîne, ce que j’appelle l‘inception de gabarit).
  • Si une valeur n’est pas une chaîne, celle-ci sera convertie en une chaîne grâce aux règles habituelles. Ainsi, si action est un objet, sa méthode toString() sera invoquée.
  • Si vous avez besoin d’écrire un accent grave à l’intérieur d’un gabarit de chaîne, il faudra l’échapper avec une barre oblique inversée (ou backslash en anglais) : `\`` sera la même chose que "`"
  • De la même manière, si vous avez besoin d’inclure les deux caractères ${ dans un gabarit de chaîne, je n’ai aucune envie de savoir ce que vous manigancez, mais vous pouvez échapper chacun de ces caractères avec une barre oblique inversée, en écrivant \${ ou $\{.

Contrairement aux chaînes ordinaires, les gabarits de chaînes peuvent s’étendre sur plusieurs lignes :

$("#warning").html(`
  <h1>Attention!</h1>
  <p>La pratique non autorisée du hockey peut résulter en 
  une sanction maximale de ${sanctionMaximale} minutes.</p>
`);

À l’intérieur d’un gabarit de chaîne, tous les espaces, y compris les sauts de ligne et l’indentation, sont inclus tels quels dans le résultat.

Bien. Au vu de la promesse faite la semaine dernière, je me sens responsable de votre santé mentale. Du coup, voici un avertissement rapide : à partir d’ici, ça va commencer à devenir intense. Vous pouvez arrêter de lire maintenant, éventuellement aller prendre une tasse de café et profiter de votre cerveau encore intact. Sérieusement, il n’y a aucune honte à repartir maintenant. Lopo Gonçalves a-t-il exploré tout l’hémisphère Sud, entièrement, une fois qu’il eut prouvé que les bateaux pouvaient traverser l’équateur sans être détruits par des monstres marins et qu’ils ne tombaient pas par delà le bord de la Terre ? Non. Il est revenu sur ses pas. Il est rentré chez lui et a profité d’un bon repas. Vous aimez les bons repas, n’est-ce-pas ?

Un accent qui assure grave

(NdT : on laissera le lecteur apprécier le jeu de mot original « Backtick the future »)

Voici ce que les gabarits de chaîne ne font pas :

  • Ils n’échappent pas automatiquement les caractères spéciaux à votre place. Afin d’éviter toute vulnérabilité liée aux attaques XSS lors de la manipulation d’éléments dynamiques, il sera nécessaire de continuer à prendre des précautions lorsqu’on utilise des données tierces, de la même façon qu’avec la concaténation classique.
  • L’interaction avec les bibliothèques d’internationalisation (des bibliothèques utilisées pour gérer, dans le code, plusieurs langues à destination des utilisateurs) n’est pas évidente. Les gabarits de chaînes ne prennent pas en charge les formats qui sont spécifiques à certaines langues pour représenter les nombres, les dates et les pluriels.
  • Ce ne sont pas des remplaçants pour les moteurs de template tels que Mustache ou Nunjucks. Les gabarits de chaînes ne disposent pas d’une syntaxe native permettant de réaliser des boucles, par exemple, de construire les lignes d’un tableau HTML à partir d’un tableau JavaScript, ils ne gèrent pas les conditions (bien qu’ici, oui, on puisse utiliser l’inception de gabarit pour ça — enfin selon moi, cela ressemblerait plus à une blague).

ES6 ajoute un petit plus aux gabarits de chaînes, qui permet aux développeurs JavaScript et aux concepteurs de bibliothèques de dépasser ces limites. Ce sont ce qu’on appelle les gabarits étiquetés (tagged templates).

La syntaxe des gabarits étiquetés est simple. Ce sont des gabarits de chaînes qui possèdent une étiquette supplémentaire avant le premier accent grave. Dans notre premier exemple, l’étiquette sera SaferHTML et nous allons l’utiliser pour tenter de s’affranchir de la première limite citée avant : échapper automatiquement les caractères spéciaux.

On notera que SaferHTML n’est pas fourni dans la bibliothèque standard d’ES6. Nous allons l’implémenter ci-après.

var message =
  SaferHTML`<p>${piege.createur} vous a envoyé un piège.</p>`;

Ici, l’étiquette est l’identifiant SaferHTML, une étiquette peut également être une propriété, telle que SaferHTML.escape voire l’appel d’une méthode telle que SaferHTML.escape({unicodeControlCharacters:false})]] (pour être tout à fait précis, n’importe quelle expression MemberExpression ou CallExpression peut être utilisée comme étiquette).

Nous avons vu que les gabarits de chaîne sans étiquette sont des raccourcis pour la simple concaténation de chaînes. Les gabarits de chaînes étiquetés représentent des raccourcis pour quelque chose de complètement différent : l’appel de fonction.

Le code ci-dessus est équivalent à :

var message =
  SaferHTML(templateData, piege.createur);

templateData est un tableau immuable, contenant tous les fragments de chaîne du gabarit, créé pour nous par le moteur de JavaScript. Ce tableau comporterait deux éléments, correspondant aux deux fragments du gabarit étiqueté, séparés par une substitution. templateData serait donc similaire à Object.freeze(["<p>","vous a envoyé un piege.</p>"]).

(En fait, il existe une autre propriété présente sur l’objet templateData. Je ne vais pas l’utiliser dans cet article, mais je la mentionnerai ici pour être tout à fait complet : templateData.raw est un autre tableau qui contient l’ensemble des fragments de chaîne présents dans le gabarit étiqueté dont chacun exactement représenté de la façon dont il est présent dans le code source. Ainsi les séquences comme \n resteront intactes et ne seront pas converties en retour à la ligne. L’étiquette standard String.raw utilise ces chaînes-là.)

Ceci offre la liberté nécessaire à la fonction SaferHTML pour interpréter à la fois la chaîne et les éléments de substitution d’une infinité de façons possibles.

Avant de continuer votre lecture, vous voudrez peut-être essayer de trouver ce que SaferHTML est capable de faire et tenter de l’implémenter ? Après tout, ce n’est qu’une simple fonction. Vous pouvez vous servir de la console de développement de Firefox pour tester votre code. Voici une des réponses possibles (disponible également sur ce gist).

function SaferHTML(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);

    // On échappe les caractères spéciaux pour la substitution.
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // On n'échappe pas les caractères spéciaux dans le gabarit.
    s += templateData[i];
  }
  return s;
}

Avec cette définition, le gabarit étiqueté SaferHTML`<p>${piege.createur} vous a envoyé un piège.</p>' devrait s’étendre afin de former la chaîne de caractères "<p>ES6&lt;3er vous a envoyé un piège.</p>". Vos utilisateurs sont en sécurité, même si un utilisateur doté d’intentions malveillantes, par exemple, PirateGérard <script>alert('xss');</script>, leur envoie un piège (si tant est que cela veuille dire quelque chose).

(En parlant des arguments, si la façon dont l’objet arguments est géré vous semble un peu maladroite, passez la semaine prochaine : il y a une autre fonctionnalité ES6 dont je pense qu’elle vous plaira.)

Un seul exemple n’est pas suffisant pour illustrer la flexibilité des gabarits étiquetés. Revisitons la liste précédente des limitations qui s’appliquent aux gabarits de chaînes de caractères afin de voir ce qu’on pourrait faire d’autre.

  • Les gabarits de chaînes de caractère n’échappent pas les caractères spéciaux de façon automatique. Mais, comme nous l’avons vu avec les gabarits étiquetés, vous pouvez résoudre ce problème vous-même avec une étiquette.

  • En fait, vous pouvez faire beaucoup mieux que ça.

    Du point de vue de la sécurité, ma fonction SaferHTML est plutôt faible. Plusieurs endroits du HTML possèdent des caractères spéciaux différents qui ont besoin d’être échappés de manières différentes ; SaferHTML ne les échappe pas tous. Cependant, en faisant quelques efforts, vous pourriez écrire une fonction SaferHTML beaucoup plus intelligente, qui analyse les fragments de HTML contenus dans les chaînes de templateData afin de discriminer les substitutions qui concernent du code HTML

    • celles qui sont des attributs d’éléments pour lesquelles on doit échapper ' et " ;
    • celles qui sont des chaînes de requête d’une URL pour lesquelles on doit échapper les caractères spéciaux des URL (plutôt que ceux spéciaux pour le code HTML).

    Cette meilleure version pourrait appliquer l’échappement pertinent pour chacune des substitutions.

    Est-ce que cela ne semble pas un peu farfelu ? En effet, l’analyse du code HTML est lente. Heureusement, les fragments des chaînes de caractères étiquetées ne changent pas lorsqu’un gabarit est à nouveau évalué. SaferHTML pourrait donc mettre en cache les résultats de l’analyse afin d’accélérer les prochains appels (ce cache pourrait être représenté par un objet WeakMap, une autre fonctionnalité de ES6 que nous verrons dans un prochain billet)

  • Les gabarits de chaînes de caractères n’ont pas de fonctionnalité d’internationalisation native. Toutefois, avec les étiquettes, il est possible d’en ajouter. Un billet de blog écrit par Jack Hsu illustre les premières étapes de ce chemin. Voici un exemple pour vous mettre en appétit :

    i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.`
    // => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.
    

    Notez comment, dans cet exemple, le nom et le montant sont des variables JavaScript. On observe aussi un peu d’un autre code : ce :c(CAD), que Jack place au sein de la chaîne de caractères du gabarit. Les variables JavaScript sont, bien sûr, gérées par le moteur JavaScript, les parties spéciales de la chaîne de caractères sont gérées par l’étiquette i18n de Jack. Si on regarde la documentation, on apprend que :c(CAD) représente une devise : le dollar Canadien.

    C’est le rôle des gabarits étiquetés

  • Les gabarits de chaînes de caractère ne sont pas des remplaçants pour les bibliothèque Moustache et Nunjucks, en partie parce qu’ils ne possèdent pas de syntaxe native pour les boucles et les conditions. Avec ce qu’on a vu, on peut réparer ça, non ? Lorsque JS ne fournit pas la fonctionnalité attendue, il suffit d’écrire une étiquette pour cela.

        // Langage de template hypothétique basé sur les
        // gabarits étiquetés ES6.
        var libraryHtml = hashTemplate`
          <ul>
            #for book in ${myBooks}
              <li><i>#{book.title}</i> by #{book.author}</li>
            #end
          </ul>
        `;
    

La flexibilité ne s’arrête pas là. Notez que les arguments d’une fonction étiquette ne sont pas automatiquement convertis en chaînes de caractères. Ils peuvent être n’importe quoi. Il en va de même pour la valeur de retour. Il n’est même pas nécessaire que les gabarits étiquetés soient des chaînes de caractères ! Il est possible d’utiliser des étiquettes personnalisées pour créer des expressions rationnelles, des arbres DOM, des images, des promesses représentant des processus asynchrones, des structures de données JS, des shaders GL…

Les gabarits étiquetés permettent aux concepteurs des bibliothèques de créer des langages à part entière pour des domaines spécifiques. Ces langages ne ressemblent aucunement à JS mais ils peuvent être intégrés à JS sans anicroche et interagir intelligemment avec le reste du langage. À première vue, je ne connais pas de telle fonctionnalité dans les autres langages. Je ne sais pas où cette fonctionnalité va nous emmener. Les possibilités sont formidables.

Quand puis-je commencer à utiliser cette fonctionnalité ?

Côté serveur, les modèles de chaînes de caractères ES6 sont supportés sur io.js dès aujourd’hui.

Côté navigateur, Firefox 34 et les versions ultérieures supportent les gabarits de chaînes de caractères. Ils ont été implémentés par Guptha Rajagopa lors de son projet de stage l’été dernier. Les gabarits de chaînes de caractères sont également supportés par Chrome 41 et les versions ultérieures, en revanche ils ne sont pas présents sous IE et Safari. Pour l’instant, vous aurez besoin d’utiliser Babel ou Traceur si vous souhaitez utiliser des gabarits de chaînes de caractères sur le web. Vous pouvez aussi les utiliser immédiatement avec TypeScript !

Attendez — et à propos de Markdown?

Hmm ?

Oh… C’est une bonne question.

(Cette section ne concerne pas vraiment JavaScript. Si vous n’utilisez pas Markdown, vous pouvez passez outre.) Avec les gabarits de chaînes de caractères, Markdown et JavaScript utilisent tous les deux le caractère ` pour signifier quelque chose de spécial. Pour le langage Markdown, ce caractère est utilisé comme séparateur afin de représenter du code au milieu d’une ligne de texte. Cela pose donc un problème, si vous écrivez ceci dans un document écrit en Markdown :

Pour afficher un message, il faut écrire `alert(`coucou monde !`)`.

Il sera affiché de la façon suivante :

Pour afficher un message, il faut écrire alert(coucou monde !).

Notez qu’il n’y a pas d’accent grave dans le résultat produit. Les quatre accents graves ont été interprétés en Markdown comme séparateurs pour le code et ont été remplacés par les balises HTML correspondantes.

Pour éviter cela, nous allons nous servir d’une fonctionnalité peu connue, présente en Markdown depuis ses débuts : utiliser plusieurs accents graves pour marquer du code, de cette façon :

Pour afficher un message, écrivez ``alert(`coucou monde !`)``.

Ce gist contient tous les détails et vu qu’il est écrit en Markdown vous pouvez aussi consulter le code source.

La suite

La semaine prochaine, nous nous attarderons sur deux fonctionnalités appréciées des programmeurs dans d’autres langages depuis des décennies : l’une qui concerne ceux qui évitent d’avoir à utiliser un argument dès que possible, et l’autre qui concerne ceux pour qui il en faut toujours beaucoup (on parle ici des arguments de fonction). Ces deux fonctionnalités sont vraiment utiles pour chacun d’entre nous.

Nous verrons celles-ci à travers les yeux de la personne qui les a implémentées dans Firefox. La semaine prochaine, ce sera Benjamin Peterson qui sera invité pour présenter, en détails, les paramètres par défaut et les paramètres du reste (rest parameters).