¿Qué es MagicString y estas líneas son tan mágicas?

MagicString es una biblioteca poco conocida. A pesar de esto, resuelve uno de los problemas más acuciantes: cambiar el código fuente utilizando su estructura (AST - árbol de sintaxis abstracta).

En este artículo aprenderemos qué es MagicString y si estas líneas son realmente "mágicas". Esto nos ayudará a comprender el próximo artículo en el que explicaré cómo logramos traducir la documentación angular tan rápidamente, y cómo ayudará con la creación de un traductor universal para Markdown y archivos de cualquier otro formato.





Hace 2 semanas publiqué la documentación en idioma ruso de Angular ( angular24.ru ). Durante este tiempo, se agregaron 35 problemas con correcciones en el texto y 2 solicitudes de extracción. Dudo sinceramente que el sistema en el que selecciona el texto, ofrezca una traducción y emita automáticamente en GitHub funcionará. ¡Pero el crowdsourcing funciona! :) Puedes aprender más sobre esto en este artículo .

Después del lanzamiento, una de las preguntas más frecuentes fue: "¿Por qué?". La pregunta es absolutamente correcta, pero para responderla, primero debe comprender qué es MagicString, cómo funciona y cómo es útil.

Supongamos que tenemos un código fuente simple:

const a = 1;

Queremos reemplazar const con var . La solución más simple es reemplazar const con var con el String.prototype.replace habitual . Y para esta tarea, esta es probablemente la solución más correcta. Pero, ¿qué sucede si necesitamos reemplazar const con var solo en el ámbito global? ¿Pero no los reemplaza dentro de las funciones? Por supuesto, puede encontrar una regularidad más compleja o escribir código complicado, pero hay una forma más escalable y flexible.

Podemos usar los analizadores para obtener AST - Árbol de sintaxis abstracta. Si está interesado en qué es AST, vaya a astexplorer.net . En esencia, es un árbol que muestra con precisión la estructura de su código.

Además, cada uno de los nodos en este AST tiene un comienzoe índices finales que indican las posiciones de estos elementos en el código fuente. Conociendo estas coordenadas y teniendo a mano la estructura del documento, podemos hacer reemplazos y permutaciones complejas con la preservación de la estructura del documento.



Por lo general, el reemplazo se realiza utilizando el diseño de patrón de visitante y varios ayudantes , que generalmente se envuelven en una sola biblioteca, que se puede llamar la "API del transformador". Cada analizador tiene su propia "API de transformador".

Dichas bibliotecas son muy fáciles de usar, pero tienen varios problemas. Uno de ellos es el rendimiento.

Como cada nodo (bueno, casi todos) en el árbol AST contiene coordenadas, al cambiar 1 nodo a menudo necesitamos actualizar las coordenadas para el resto del árbol. Aquí puede argumentar que puede hacerlo con poca sangre: no actualice las coordenadas en todas partes, sino que simplemente devuelva el AST al texto según la estructura. Pero hay un problema: perderá inmediatamente el formato del texto original, lo que contradice nuestra tarea: reemplazar const con var en la línea existente. De hecho, obtenemos una nueva línea con un nuevo formato. Y si esto no es un problema para una línea pequeña, imagine un archivo de 1000 líneas en el que el formato ha cambiado completamente debido a la sustitución de const por var . Eso no suena muy bien.



Y aquí viene la magia de MagicString. La primera vez que supe de su existencia fue gracias al proyecto Rich Harris, que se llamaba butternut . Butternut es un minificador de JavaScript. Se dijo que Butternutt era 3 veces más rápido que UglifyJS y 10-15 veces más rápido que Babili . Seguiré adelante y diré que el proyecto se cubrió con una cuenca de cobre hace al menos 3 años. Pero incluso entonces, estaba intrigado por el secreto de su desempeño. Fue un MagicString.

Echemos un vistazo al trabajo con MagicString:

var MagicString = require( 'magic-string' );
var s = new MagicString( 'const a = 1; const b = 2;' );

s.overwrite( 0, 5, 'var' );
s.toString(); // 'var a = 1; const b = 2;'

//  

El algoritmo de MagicString es muy simple: envolvemos la cadena original en un objeto en el que no aplicamos directamente los cambios a la cadena, pero agregamos las coordenadas y lo que debe hacerse en una matriz para el futuro. Y solo cuando alguien quiere obtener la fila resultante, comenzamos 1 a 1 para realizar las operaciones acumuladas. Por ejemplo:

  1. Reemplazamos const por var, comenzando en el índice 0 y terminando en el índice 5
  2. Sabemos que todos los reemplazos posteriores deben tener un índice menor que 2 (var menos que constante por 2 caracteres, una línea más corta)
  3. Actualizamos las coordenadas de todas las operaciones.
  4. Aplicamos la siguiente operación, etc.




Todo se ve bastante simple. Pero, ¿por qué MagicString es más rápido? La respuesta es bastante simple: la cantidad de operaciones que realizamos en nuestro árbol es mucho menor que la cantidad de nodos AST. Sin mencionar la cantidad de memoria necesaria para el AST y el hecho de que Tree Traversal (viajar a través de un árbol) no es una operación libre, sino O (n + m)



¿Y si estoy listo para esperar media hora extra? Y aquí viene la segunda ventaja de MagicString. Cada analizador inventa su propia API para la transformación. Y esto sigue siendo muy bueno, si existe una API de este tipo (no todos los analizadores lo proporcionan), muy a menudo nos quedamos sin la capacidad de reemplazar normalmente el texto fuente usando AST. Pero MagicString es una única API universal para cambiar la cadena de origen. No importa qué analizador o combinación de analizadores haya utilizado. Con MagicString, puede trabajar igualmente bien con cualquier AST.



Espero que estés interesado en MagicString. En el próximo artículo hablaré sobre el doble MagicString y cómo hacer un traductor universal de documentos Markdown.

Suscríbase a mi canal de Telegram @obenjiro_notes y Twitter obenjiropara no perderse los siguientes artículos sobre el tema y muchas otras cosas interesantes.

All Articles