Présentation des mappages source JavaScript

Avez-vous déjà souhaité pouvoir lire votre code côté client et, surtout, le déboguer, même après l'avoir combiné et réduit, sans nuire aux performances ? C'est désormais possible grâce à la magie des cartes sources.

Les cartes sources sont un moyen de mapper un fichier combiné/minifié à un état non construit. Lorsque vous compilez votre application pour la production, tout en minimisant et en combinant vos fichiers JavaScript, vous générez un mappage source qui contient des informations sur vos fichiers d'origine. Lorsque vous interrogez une ligne et un numéro de colonne spécifiques dans le code JavaScript généré, vous pouvez effectuer une recherche dans la carte source qui renvoie l'emplacement d'origine. Les outils pour les développeurs (actuellement, les versions nocturnes de WebKit, Google Chrome ou Firefox 23 ou version ultérieure) peuvent analyser automatiquement la carte source et donner l'impression que vous exécutez des fichiers non réduits et non combinés.

Cette démonstration vous permet d'effectuer un clic droit n'importe où dans la zone de texte contenant la source générée. Sélectionnez "Get original location" (Obtenir l'emplacement d'origine) pour interroger la carte source en transmettant la ligne et le numéro de colonne générés, et renvoyer la position dans le code d'origine. Assurez-vous que la console est ouverte afin que vous puissiez voir le résultat.

Exemple de bibliothèque de mappage source JavaScript Mozilla JavaScript en action.

Situation réelle

Avant de visualiser l'implémentation concrète suivante des cartes sources, assurez-vous d'avoir activé la fonctionnalité des cartes sources dans Chrome Canary ou WebKit chaque nuit. Pour ce faire, cliquez sur la roue dentée des paramètres dans le panneau des outils de développement et cochez l'option "Activer les cartes sources".

Activer les mappages sources dans les outils de développement WebKit

Dans les versions 23 et ultérieures de Firefox, les mappages sources sont activés par défaut dans les outils de développement intégrés.

Activation des mappages source dans les outils de développement Firefox

Pourquoi devrais-je m'intéresser aux mappages sources ?

À l'heure actuelle, le mappage source ne fonctionne qu'entre le code JavaScript non compressé/combiné et le code JavaScript compressé/non combiné. Toutefois, l'avenir s'annonce radieux en parlant de langages compilés en JavaScript tels que CoffeeScript, et même avec la possibilité d'ajouter la prise en charge des préprocesseurs CSS tels que SASS ou LESS.

À l'avenir, il serait facile d'utiliser presque tous les langages comme si ceux-ci étaient pris en charge nativement dans le navigateur avec des cartes sources:

  • CoffeeScript
  • ECMAScript 6 et versions ultérieures
  • SASS/LESS et autres
  • Presque tous les langages de compilation en JavaScript

Regardez cet enregistrement d'écran de CoffeeScript en cours de débogage dans une version expérimentale de la console Firefox:

Google Web Toolkit (GWT) est depuis peu compatible avec les cartes sources. Ray Cromwell, de l'équipe GWT, a réalisé un enregistrement d'écran exceptionnel montrant la prise en charge des cartes sources en action.

Un autre exemple que j'ai créé utilise la bibliothèque Traceur de Google, qui vous permet d'écrire ES6 (ECMAScript 6 ou Next) et de le compiler dans du code compatible ES3. Le compilateur Traceur génère également une carte source. Regardez cette démonstration des caractéristiques et classes ES6 utilisées de manière native dans le navigateur, grâce à la carte source.

La zone de texte de la démonstration vous permet également d'écrire ES6, qui sera compilé instantanément et générera une carte source ainsi que le code ES3 équivalent.

Débogage de Traceur ES6 à l'aide de cartes sources

Démonstration: Écrire ES6, le déboguer et voir le mappage source en action

Comment le mappage source fonctionne-t-il ?

Pour le moment, le seul compilateur/minificateur JavaScript compatible avec la génération de cartes sources est le compilateur Closure. (Je vous expliquerai comment l'utiliser plus tard.) Une fois que vous avez combiné et réduit la taille de votre code JavaScript, vous disposez d'un fichier de mappage source.

Actuellement, le compilateur Closure n'ajoute pas le commentaire spécial à la fin qui est requis pour indiquer aux outils de développement du navigateur qu'une carte source est disponible:

//# sourceMappingURL=/path/to/file.js.map

Cela permet aux outils de développement de mapper les appels vers leur emplacement dans les fichiers sources d'origine. Auparavant, le pragma de commentaire était //@, mais en raison de certains problèmes liés à cela et aux commentaires de compilation conditionnelle dans IE, la décision a été prise de le remplacer par //#. Actuellement, Chrome Canary, WebKit Nightly et Firefox 24 (ou version ultérieure) sont compatibles avec le nouveau système de commentaire. Ce changement de syntaxe concerne également l'URL source.

Si vous n'aimez pas l'idée de ce commentaire bizarre, vous pouvez définir un en-tête spécial pour votre fichier JavaScript compilé:

X-SourceMap: /path/to/file.js.map

Cliquez sur "J'aime" pour le commentaire afin d'indiquer à l'utilisateur de la carte source où rechercher la carte source associée à un fichier JavaScript. Cet en-tête aborde également le problème du référencement des cartes sources dans les langues qui n'acceptent pas les commentaires sur une seule ligne.

Exemple de mappage source activé dans les outils pour les développeurs WebKit et de mappage source désactivé.

Le fichier de carte source ne sera téléchargé que si vous avez activé les mappages sources et que vos outils de développement sont ouverts. Vous devez également importer vos fichiers d'origine afin que les outils de développement puissent les référencer et les afficher si nécessaire.

Comment générer une carte source ?

Vous devez utiliser le compilateur de fermeture pour réduire, concaténer et générer un mappage source pour vos fichiers JavaScript. La commande est la suivante:

java -jar compiler.jar \
--js script.js \
--create_source_map ./script-min.js.map \
--source_map_format=V3 \
--js_output_file script-min.js

Les deux options de commande importantes sont --create_source_map et --source_map_format. Cette étape est obligatoire, car la version par défaut est V2 et nous souhaitons travailler uniquement avec V3.

Anatomie d'une carte source

Pour mieux comprendre une carte source, nous allons prendre un petit exemple de fichier de carte source qui serait généré par le compilateur Closure et nous pencher plus en détail sur le fonctionnement de la section "mappings". L'exemple suivant diffère légèrement de l'exemple de la spécification V3.

{
    version : 3,
    file: "out.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}

Vous pouvez voir ci-dessus qu'une carte source est un littéral d'objet qui contient de nombreuses informations intéressantes:

  • Numéro de version sur lequel est basé le mappage source
  • Nom de fichier du code généré (votre fichier de production mini-fédé/combiné)
  • sourceRoot vous permet d'ajouter une structure de dossiers au début des sources. Cette technique permet également de gagner de l'espace.
  • sources contient tous les noms de fichiers qui ont été combinés
  • contient tous les noms de variables/méthodes qui apparaissent dans votre code.
  • Enfin, c'est dans la propriété "mappings" que la magie opère à l'aide des valeurs VLQ en base64. C'est ici que se passe le véritable gain d'espace.

Base64 VLQ et maintien de la petite taille de la carte source

À l'origine, la spécification de la carte source affichait une sortie très détaillée de tous les mappages, ce qui entraînait une taille environ 10 fois supérieure du code généré. La version 2 a réduit ce résultat d'environ 50 %, tandis que la version 3 l'a réduit à nouveau de 50 %. Pour un fichier de 133 Ko, vous obtenez donc une carte source d'environ 300 Ko.

Alors, comment ont-ils réduit la taille tout en conservant les mappages complexes ?

Le paramètre VLQ (Variable Length Quantity) est utilisé pour encoder la valeur en base64. La propriété mappings est une très grande chaîne. Cette chaîne contient des points-virgules (;) qui représentent un numéro de ligne dans le fichier généré. Au sein de chaque ligne, les virgules (,) représentent les segments de cette ligne. Chacun de ces segments correspond à 1, 4 ou 5 dans les champs de longueur variable. Certaines peuvent sembler plus longues, mais elles contiennent des bits de continuation. Chaque segment s'appuie sur le précédent, ce qui permet de réduire la taille du fichier puisque chaque bit est lié à ses segments précédents.

Répartition d'un segment dans le fichier JSON de carte source.

Comme indiqué ci-dessus, chaque segment peut avoir une longueur variable de 1, 4 ou 5. Ce diagramme est considéré comme une longueur variable de quatre avec un bit de continuation (g). Nous allons décomposer ce segment et vous montrer comment la carte source calcule le lieu d'origine.

Les valeurs ci-dessus sont purement décodées en base64. Le traitement est plus complexe pour obtenir leurs valeurs réelles. Chaque segment correspond généralement à cinq éléments:

  • Colonne générée
  • Fichier d'origine dans lequel cet élément figure
  • Numéro de ligne d'origine
  • Colonne d'origine
  • Si possible, le nom d'origine

Les segments n'ont pas tous un nom, un nom de méthode ou un argument. Les segments vont donc passer de quatre à cinq longueurs variables. La valeur g dans le diagramme des segments ci-dessus correspond à ce que l'on appelle un bit de continuation. Il permet une optimisation plus poussée lors de l'étape de décodage VLQ en base64. Un bit de continuation vous permet de vous appuyer sur une valeur de segment afin de pouvoir stocker de grands nombres sans avoir à en stocker un grand nombre, une technique très intelligente de gain d'espace qui trouve ses racines dans le format midi.

Une fois traité davantage, le schéma ci-dessus AAgBC renverrait 0, 0, 32, 16, 1, le 32 étant le bit de continuation qui permet de créer la valeur suivante de 16. B purement décodé en Base64 correspond à 1. Ainsi, les valeurs importantes qui sont utilisées sont 0, 0, 16, 1. Cela nous indique ensuite que la ligne 1 (les lignes sont conservées par les points-virgules) que la colonne 0 du fichier généré correspond au fichier 0 (le tableau des fichiers 0 est foo.js), et la ligne 16 à la colonne 1.

Pour expliquer comment les segments sont décodés, je vais vous référer à la bibliothèque JavaScript de cartes sources de Mozilla. Vous pouvez également consulter le code de mappage source des outils de développement WebKit, également écrit en JavaScript.

Pour bien comprendre comment nous obtenons la valeur 16 à partir de B, nous devons avoir des connaissances de base sur les opérateurs bit à bit et sur le fonctionnement de la spécification pour le mappage source. Le chiffre précédent, g, est signalé comme un bit de continuation en comparant le chiffre (32) et le VLQ_CONTINUATION_BIT (binaire 100000 ou 32) à l'aide de l'opérateur AND (&) au niveau du bit.

32 & 32 = 32
// or
100000
|
|
V
100000

Cela renvoie un 1 à chaque position de bit où les deux le font apparaître. Ainsi, une valeur décodée en base64 de 33 & 32 renverrait 32, car elle ne partage que la position de 32 bits, comme vous pouvez le voir dans le schéma ci-dessus. Cela augmente ensuite la valeur de décalage de 5 pour chaque bit de continuation précédent. Dans le cas ci-dessus, il n'a été décalé qu'une seule fois de 5, soit un décalage de 1 (B) par 5 vers la gauche.

1 <<../ 5 // 32

// Shift the bit by 5 spots
______
|    |
V    V
100001 = 100000 = 32

Cette valeur est ensuite convertie à partir d'une valeur signée VLQ en décalant le nombre (32) d'un côté droit.

32 >> 1 // 16
//or
100000
|
 |
 V
010000 = 16

Voilà, c'est ainsi que vous passez de 1 à 16. Ce processus peut sembler trop compliqué, mais une fois que les chiffres commencent à augmenter, c'est plus logique.

Problèmes XSSI potentiels

La spécification mentionne les problèmes d'inclusion de scripts intersites pouvant provenir de l'utilisation d'une carte source. Pour éviter cela, nous vous recommandons d'ajouter ")]}" au début de la première ligne de votre mappage source afin d'invalider délibérément JavaScript et de générer une erreur de syntaxe. Les outils de développement WebKit sont déjà en mesure de gérer cela.

if (response.slice(0, 3) === ")]}") {
    response = response.substring(response.indexOf('\n'));
}

Comme indiqué ci-dessus, les trois premiers caractères sont découpés pour vérifier s'ils correspondent à l'erreur de syntaxe dans la spécification et, le cas échéant, supprime tous les caractères menant à la première nouvelle entité de ligne (\n).

sourceURL et displayName en action: fonctions d'évaluation et anonymes

Bien qu'elles ne fassent pas partie des spécifications de la carte source, les deux conventions suivantes vous permettent de faciliter le développement lorsque vous travaillez avec des évaluations et des fonctions anonymes.

Le premier assistant ressemble beaucoup à la propriété //# sourceMappingURL et est en fait mentionné dans la spécification du mappage source V3. En incluant le commentaire spécial suivant dans votre code, qui sera évalué, vous pouvez nommer les évaluations de sorte qu'elles apparaissent comme des noms plus logiques dans vos outils de développement. Découvrez une démonstration simple à l'aide du compilateur CoffeeScript:

Démonstration: voir le code de eval() s'afficher sous forme de script via sourceURL

//# sourceURL=sqrt.coffee
À quoi ressemble un commentaire spécial sourceURL dans les outils de développement ?

L'autre outil d'aide vous permet de nommer des fonctions anonymes à l'aide de la propriété displayName disponible dans le contexte actuel de la fonction anonyme. Profilez la démonstration suivante pour voir la propriété displayName en action.

btns[0].addEventListener("click", function(e) {
    var fn = function() {
        console.log("You clicked button number: 1");
    };

    fn.displayName = "Anonymous function of button 1";

    return fn();
}, false);
Affichage de la propriété &quot;displayName&quot; en action.

Lors du profilage de votre code dans les outils de développement, la propriété displayName s'affichera plutôt que (anonymous). Cependant, displayName est plutôt mort dans l'eau et il ne sera pas intégré à Chrome. Cependant, tout espoir n'est pas perdu, et une bien meilleure proposition, appelée debugName, a été proposée.

Au moment où nous écrivons ces lignes, l'attribution de noms n'est possible que dans les navigateurs Firefox et WebKit. La propriété displayName ne se trouve que dans les boîtes de nuit WebKit.

Unissons-nous

Actuellement, l'ajout de la prise en charge des cartes sources à CoffeeScript fait l'objet d'une longue discussion. Consultez le problème et ajoutez votre assistance pour ajouter la génération de mappage source au compilateur CoffeeScript. Il s'agira d'une grande victoire pour CoffeeScript et ses fidèles abonnés.

UglifyJS présente également un problème de carte source que vous devez examiner.

Un grand nombre d'tools permettent de générer des cartes sources, y compris le compilateur Coffeescript. Je considère qu'il s'agit maintenant d'un point néfaste.

Plus nous disposons d'outils capables de générer des cartes sources, mieux nous nous en sortirons. N'hésitez donc pas à demander de l'aide à votre projet Open Source préféré ou à ajouter la prise en charge de ce type de carte à votre projet Open Source préféré.

Ce n'est pas parfait

Les mappages sources ne prennent pas en charge les expressions de contrôle pour le moment. Le problème est que la tentative d'inspection d'un nom d'argument ou de variable dans le contexte d'exécution actuel ne renverra rien, car cet argument n'existe pas réellement. Cela nécessiterait une sorte de mappage inversé pour rechercher le nom réel de l'argument ou de la variable que vous souhaitez inspecter par rapport au nom réel de l'argument ou de la variable dans votre code JavaScript compilé.

Bien sûr, il s'agit d'un problème à résoudre. En nous concentrant davantage sur les mappages de sources, nous pouvons commencer à voir des fonctionnalités incroyables et une meilleure stabilité.

Problèmes

Depuis peu, jQuery 1.9 est compatible avec les mappages sources en cas de diffusion depuis des CDN officiels. Elle indiquait également un bug particulier lors de l'utilisation de commentaires de compilation conditionnelle dans IE (//@cc_on) avant le chargement de jQuery. Depuis, un commit a été effectué pour atténuer ce problème en encapsulant sourceMappingURL dans un commentaire sur plusieurs lignes. La leçon à apprendre n'utilise pas de commentaire conditionnel.

Ce problème a depuis été résolu lors du remplacement de la syntaxe par //#.

Outils et ressources

Voici d'autres ressources et outils à consulter:

Les cartes sources sont un utilitaire très puissant proposé aux développeurs. Il est très utile de pouvoir garder une application Web légère mais facilement débogable. Il s'agit également d'un outil d'apprentissage très puissant qui permet aux nouveaux développeurs de voir avec quel degré d'expérience ils structurent et écrivent leurs applications, sans avoir à parcourir du code minimisé illisible.

N'attendez plus ! Commencez à générer des mappages sources pour tous les projets dès maintenant.