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 "Obtenir l'emplacement d'origine". va 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.
Situation réelle
Avant d'afficher l'implémentation suivante des cartes sources en conditions réelles, 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, puis cochez la case "Activer les cartes sources". .
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.
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é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.
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 pencherons plus en détail sur la façon dont les « mappages » fonctionne correctement. 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 taille de la carte source réduite
À 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 par rapport à ses segments précédents.
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 limiter ce problème, nous vous recommandons d'ajouter ")]}
" au début de la première ligne de votre carte source invalider délibérément JavaScript afin qu'une erreur de syntaxe soit générée. 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 V3 de la carte source. 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
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);
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. Vérifiez 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'outils 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
Récemment, jQuery 1.9 est désormais 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 été résolu depuis le remplacement de la syntaxe par //#
.
Outils et ressources
Voici d'autres ressources et outils à consulter:
- Nick Fitzgerald dispose d'une section UglifyJS compatible avec la carte source.
- Paul Ireland propose une petite démonstration pratique présentant des cartes sources.
- Consultez la liste de modifications de WebKit pour savoir quand cette application est abandonnée.
- L'ensemble de modifications comprenait également un test de mise en page, ce qui a permis le lancement de cet article.
- Mozilla a un bug que vous devez suivre concernant l'état des cartes sources dans la console intégrée.
- Conrad Irwin a écrit un joyau de carte source très utile pour tous les utilisateurs Ruby.
- Vous trouverez plus d'informations sur l'attribution de noms des évaluations et la propriété displayName.
- Vous pouvez consulter la source de Closure Compilers pour créer des mappages sources.
- Vous trouverez des captures d'écran concernant la compatibilité des cartes sources GWT.
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.