Contribuer à Babel : trois leçons à retenir
Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →
Se familiariser avec une nouvelle base de code présente toujours ses défis, et Babel ne fit pas exception.
J'ai travaillé sur Babel dans le cadre du programme Google Summer of Code 2017, en mettant à jour les transformations de Babel et le parser Babylon pour intégrer les évolutions des spécifications et implémenter de nouvelles fonctionnalités.
Voici quelques enseignements que j'ai tirés de cette aventure jusqu'à présent.
1. Oui, la communication est cruciale
Pour commencer à explorer la base de code, j'ai parcouru la liste des issues ouvertes sur Babel et identifié une tâche relativement simple (issue #5728).
Pour m'assurer de bien comprendre, j'ai posé une question rapide dans le fil de discussion :
Après clarification, j'ai modifié le plugin pour qu'il ne lève plus d'erreurs "runtime" lors de la transpilation, mais uniquement lors de l'exécution réelle du code. Un extrait problématique m'a sauté aux yeux :
for (const violation of (binding.constantViolations: Array)) {
throw violation.buildCodeFrameError(messages.get("readOnly", name));
}
Le travail consistait ici à insérer une instruction throw dans le code généré, ce qui ne fut pas trop complexe. Cependant, certains cas persistaient où des erreurs runtime survenaient ailleurs dans du code non directement lié à ce fichier.
Voulant explorer d'autres parties de la base de code Babel, j'ai reporté ce point à plus tard.
Peu après, je reçus une mise à jour... pour le moins surprenante sur l'issue. Attends, quoi ?
Je n'avais jamais explicitement dit que je travaillais sur la résolution du problème, supposant que mon message impliquerait mon intention de le traiter.
Oups.
2. Les limites des tests par snapshot
Repartant en exploration, je tombai sur issue #5656 :
Arguments désoptimisés lors d'un masquage dans une fonction imbriquée
Il s'agit d'une demande de fonctionnalité (je pense). Les arguments ne sont pas optimisés si une fonction interne masque le nom avec un paramètre (ou des paramètres rest dans mon cas).
Code d'entrée
JavaScriptconst log = (...args) => console.log(...args);
function test_opt(...args) {
log(...args);
}
function test_deopt(...args) {
const fn = (...args) => log(...args);
fn(...args);
}...
Comportement attendu vs. actuel
Je m'attendais à ce que le code soit optimisable pour utiliser .apply( thisArg, arguments ) partout. Or, dans test_deopt, le ...args externe est copié uniquement pour être passé à la fonction interne fn. Je constate que le problème disparaît si je renomme soit le ...args de test_deopt, soit celui de la fonction fléchée fn.
Que se passe-t-il ?
Le comportement observé était que ce code générait :
var log = function log() {
var _console;
return (_console = console).log.apply(_console, arguments);
};
function test_opt() {
log.apply(undefined, arguments);
}
function test_deopt() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { // unnecessary loop
args[_key] = arguments[_key];
}
var fn = function fn() {
return log.apply(undefined, arguments);
};
fn.apply(undefined, args);
}
Voyez-vous cette boucle for ? Normalement nécessaire car l'objet arguments n'est pas un vrai tableau — par exemple, arguments.slice() échouerait lamentablement.
Cependant, ici il est simplement passé à Function.prototype.apply. Étonnamment, Babel optimise déjà ce cas spécifique, comme dans l'exemple test_opt ci-dessus.
Tentative de correction
Mon approche ? Ajouter le fichier problématique comme nouveau cas de test, puis tenter d'obtenir la sortie souhaitée.
« Pourquoi le test échoue ? Si je le modifie un peu, ça devrait se résoudre. »
Malgré des appels répétés à make test-only et des modifications des transformations d'identifiants dans le code, chaque changement provoquait simplement l'échec d'un autre groupe de tests.
Le débogueur Chromium est « passionnant »
Démoralisé, agacé et confus, j'ai pris la peine de lancer l'inspecteur Node.js pour comprendre ce qui se passait.
Après être revenu à mon ordinateur après une pause, je suis accueilli par le clignotement frénétique du disque dur et un ordinateur pratiquement figé.
Holding my computer together with judicious applications of Alt + SysRq + F, I managed to work through the flow of things¹ and figure out how exactly the code worked.
Même après tout ça, je ne voyais toujours pas pourquoi il décidait de supprimer ce code « nécessaire » (pensais-je) qui disparaissait avec ma correction initiale.
Le vrai problème ?
Vous voyez l'erreur ci-dessus ? Tout ce code en vert n'aurait jamais dû être là, même si c'était « attendu ».
En résumé : le test était cassé. Super. :/
La véritable correction a impliqué la création d'une fonction referencesRest pour s'assurer que l'opérateur spread s'appliquait bien au paramètre d'origine, plutôt qu'à une variable masquée dans une autre portée.
¹ : Il s'avère qu'ajouter un gros dossier à l'espace de travail DevTools provoquait des fuites mémoire jusqu'à l'OOM (bug que j'ai signalé).
Mais alors pourquoi utiliser des tests par instantané ?!
Premièrement, c'est bien plus simple de créer des tests quand il suffit de demander à Babel d'exécuter votre cas pour générer le fichier attendu. Cela nous offre une option peu coûteuse en temps tout en protégeant contre une grande partie des erreurs potentielles.
De plus, surtout pour un programme comme Babel, il serait bien plus difficile de tester autrement. Par exemple, vérifier des nœuds spécifiques de l'AST prendrait plus de temps à écrire et serait aussi sujet à des ruptures non évidentes si votre code modifie la transformation.
En bref, quelques leçons ici :
-
Assurez-vous que vos tests sont corrects dès le départ — ne soyez pas complaisant !
-
Oui, le débogueur est réellement utile pour comprendre ce qui se passe.
-
Certaines choses prennent du temps — si vous n'avancez pas, faites une pause ou travaillez sur autre chose.
3. Les réunions d'équipe !
Je sais que ça dépasse un peu la notion de « problème », mais passons :)
Quand vous travaillez sur un projet avec d'autres personnes, c'est toujours utile de se synchroniser pour discuter des domaines à aborder.
Mais comment faire exactement ?!
Beurk, les réunions.
Avec des personnes dispersées dans le monde, communiquer n'est jamais simple, mais nous devions composer avec cette réalité.
Les fuseaux horaires
Pour un projet open source mondial, choisir un horaire convenable devient vite un exercice complexe de conciliation.
Malgré nos dispersions géographiques, nous avons réussi à trouver un créneau commun.
Hélas, cela n'a pas duré. Nous avons dû alterner entre deux horaires chaque semaine (13:00 et 16:00 UTC) pour inclure d'autres contributeurs, ce qui limitait ma participation à une fois par quinzaine.
Malgré cela, nous avons réussi à réaliser des progrès significatifs en coordonnant des correctifs dans diverses parties clés de Babel, notamment le support TypeScript, les modifications de l'ordre d'exécution des plugins de transformation, ainsi que la mise à jour avec les dernières évolutions de TC39.
Et ensuite ?
Nous continuons de peaufiner Babel 7 pour une utilisation générale, avec plusieurs nouvelles fonctionnalités qui l'accompagnent.
Je travaille avec d'autres contributeurs à intégrer le support de la proposition mise à jour des Class Fields dans Babel, permettant aux développeurs de la tester et de fournir des retours.
Au passage, je tiens à remercier tous les mentors et contributeurs de Babel pour leur aide dans les revues de code et leurs conseils sur les propositions, depuis mon premier contact jusqu'à aujourd'hui.
Vous souhaitez en savoir plus sur Babel ? Consultez notre page de contribution et rejoignez la communauté Slack !
En savoir plus sur Karl
Karl Cheng est un étudiant GSoC 2017 originaire de Sydney, Australie. Retrouvez-le sur GitHub (Qantas94Heavy) et Twitter (@Qantas94Heavy) !
Consultez notre premier article sur le Summer of Code pour plus d'informations !