Contribuir a Babel: Tres lecciones para recordar
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Adentrarse en una nueva base de código siempre plantea sus desafíos, y Babel no fue la excepción.
He estado trabajando con Babel como parte del programa Google Summer of Code 2017, actualizando transformaciones de Babel y el parser Babylon para adaptarse a cambios en las especificaciones e implementar nuevas funcionalidades.
Estas son algunas lecciones que he aprendido en mis aventuras hasta ahora.
1. Sí, la comunicación es importante
Para comenzar a conocer mejor la base de código, revisé la lista de issues abiertos en Babel y encontré uno relativamente sencillo (issue #5728) para abordar.
Solo para asegurarme de que entendía lo que estaba haciendo, planteé una pregunta rápida en el hilo:
Tras obtener la aclaración, me dispuse a modificar el plugin para que no lanzara errores "runtime" durante la transpilación, sino solo cuando el código se ejecutara realmente. Un fragmento de código sospechoso destacaba:
for (const violation of (binding.constantViolations: Array)) {
throw violation.buildCodeFrameError(messages.get("readOnly", name));
}
Lo que se necesitaba hacer aquí era insertar una sentencia throw en el código generado, lo cual no resultó demasiado complicado. Sin embargo, aún había algunos casos donde se lanzaban errores runtime en otras partes del código no directamente relacionadas con este archivo.
Con ganas de explorar otras partes de la base de código de Babel, dejé eso pendiente para más adelante.
Poco después, recibí una, bueno, interesante actualización en el issue… ¿Espera qué?
Nunca dije explícitamente que estaba trabajando en solucionar el issue, pero asumí que mi publicación implicaría que iba a ocuparme de ello.
Ups.
2. Cuando las pruebas de snapshot no son suficientes
Tras emprender otra búsqueda, me encontré con issue #5656:
Argumentos desoptimizados cuando se sombrean en función anidada
Esto es una solicitud de función (creo). Los argumentos no se optimizan si una función interna sombrea el nombre con un parámetro (o parámetros rest en mi caso).
Código de entrada
JavaScriptconst log = (...args) => console.log(...args);
function test_opt(...args) {
log(...args);
}
function test_deopt(...args) {
const fn = (...args) => log(...args);
fn(...args);
}...
Comportamiento esperado vs. actual
Esperaría que el código fuera optimizable para usar .apply( thisArg, arguments ) en todo momento. Sin embargo, en test_deopt el ...args externo se copia solo para pasarse a la fn interna. Puedo verificar que el problema desaparece si renombro el ...args de test_deopt o el de la función flecha fn.
¿Qué está ocurriendo?
Lo que sucedía era que este código generaba lo siguiente:
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);
}
¿Ves esa sección for? Normalmente esto es necesario porque el objeto arguments no es un array real — por ejemplo, si intentaras ejecutar arguments.slice(), fallaría estrepitosamente.
Sin embargo, en este caso solo se está pasando a Function.prototype.apply. Sorprendentemente, Babel ya se encarga de optimizar este caso específico, como en el ejemplo de test_opt anterior.
Intentando solucionarlo
¿Qué hice entonces? Agregué el archivo problemático como nuevo caso de prueba e intenté lograr que la salida reflejara lo que deseaba.
"¿Por qué falla la prueba? Seguro que si la modifico un poco se resolverá sola."
A pesar de ejecutar repetidamente make test-only y modificar las transformaciones de identificadores referenciados, cada cambio solo resultaba en un conjunto diferente de pruebas fallidas.
El depurador de Chromium es "divertido"
Frustrado, molesto y confundido, me decidí a usar el inspector de Node.js para analizar paso a paso lo que ocurría.
Al regresar a mi computadora después de un descanso para beber, me encuentro con la luz del disco duro parpadeando frenéticamente y el equipo prácticamente colgado.
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.
Incluso después de todo eso, no lograba ver por qué estaba eliminando este código "necesario" (según mi pensamiento inicial) que mi solución original removía.
¿El problema real?
¿Ves el error mostrado arriba? Todo ese código en verde no debería estar ahí, aunque fuera "esperado".
Básicamente: la prueba estaba rota. Genial. :/
La solución real implicó crear una función referencesRest para asegurar que el operador de propagación se aplicara al parámetro original, no a una variable enmascarada en otro ámbito.
¹: Resulta que añadir una carpeta grande al workspace de DevTools provocaba fugas de memoria hasta causar un OOM (error que reporté).
¡¿Entonces por qué usamos pruebas de snapshot?!
Primero, es mucho más fácil crear pruebas cuando solo necesitas que Babel ejecute tu caso para generar el archivo esperado. Esto nos ofrece una opción de bajo costo temporal mientras protege contra un gran porcentaje de errores potenciales.
Además, especialmente para un programa como Babel, sería mucho más difícil probarlo de otras formas. Por ejemplo, podríamos verificar nodos específicos del AST, pero esto lleva más tiempo y es propenso a fallos no evidentes cuando cambias la forma de hacer la transformación.
En resumen, algunas lecciones aquí:
-
Asegúrate de que tus pruebas estén correctas desde el principio—¡no te confíes!
-
Sí, el depurador realmente es útil para ver qué ocurre.
-
A veces las cosas llevan tiempo—si no avanzas, toma un descanso o trabaja en otra cosa.
3. ¡Reuniones de equipo!
Sé que esto estira un poco la noción de "issue", pero bueno :)
Cuando trabajas en un proyecto con otras personas, siempre es útil reunirse y discutir áreas que necesitan atención.
¿Cómo hacemos eso exactamente?!
Uf, reuniones.
Con personas distribuidas por todo el mundo, comunicarse nunca es fácil, pero aún así tuvimos que ingeniárnoslas para intentarlo.
Husos horarios
En proyectos de código abierto con alcance global, elegir una hora adecuada rápidamente se convierte en un ejercicio complejo de discusión trivial.
A pesar de las grandes diferencias horarias, logramos organizar algo que funcionaba para la mayoría.
Lamentablemente, no duró. Finalmente tuvimos que alternar entre dos horarios quincenalmente (13:00 y 16:00 UTC) para acomodar a otros participantes, lo que significó que solo podía asistir cada quince días.
A pesar de esto, hemos logrado avances significativos coordinando correcciones en varias partes que conforman cambios clave en Babel, incluyendo soporte para TypeScript, modificaciones en el orden de ejecución de los plugins de transformación, y manteniéndonos al día con los cambios de TC39.
¿Qué sigue?
Seguimos puliendo Babel 7 para su consumo general, con varias características nuevas que llegarán junto con esta versión.
Estoy trabajando con varios colaboradores para incluir en Babel el soporte para la propuesta actualizada de Class Fields, permitiendo que los usuarios puedan probarla y enviar feedback.
Además, quisiera agradecer a todos los mentores y colaboradores de Babel por su ayuda con revisiones entre pares y guía en las propuestas, desde el primer contacto hasta hoy.
¿Quieres saber más sobre Babel? Visita nuestra página de contribución y únete a la comunidad de Slack!
Más sobre Karl
Karl Cheng es estudiante de GSoC 2017 originario de Sídney, Australia. Conoce más sobre él en GitHub (Qantas94Heavy) y Twitter (@Qantas94Heavy)!
¡Consulta nuestra primera publicación sobre Summer of Code para más información!