O que é MagicString e essas linhas são tão mágicas?

MagicString é uma biblioteca pouco conhecida. Apesar disso, ele resolve um dos problemas mais prementes - alterar o código-fonte usando sua estrutura (AST - abstract syntax tree).

Neste artigo, aprenderemos o que é o MagicString e se essas linhas são realmente "mágicas". Isso nos ajudará a entender o próximo artigo no qual explicarei como conseguimos traduzir a documentação do Angular tão rapidamente e como isso ajudará na criação de um tradutor universal para o Markdown e arquivos de qualquer outro formato.





Há duas semanas, liberei a documentação em russo do Angular ( angular24.ru ). Durante esse período, foram adicionadas 35 questões com correções no texto e 2 solicitações pull. Eu sinceramente duvidava que o sistema em que você selecionou o texto, ofereça uma tradução e a edição automática no GitHub funcione. Mas o crowdsourcing funciona! :) Você pode aprender mais sobre isso neste artigo .

Após o lançamento, uma das perguntas mais frequentes foi: "Por quê?". A pergunta está absolutamente correta, mas, para respondê-la, você deve primeiro entender o que é o MagicString, como funciona e como é útil.

Suponha que tenhamos um código fonte simples:

const a = 1;

Queremos substituir const por var . A solução mais simples é substituir const por var pelo String.prototype.replace usual . E para esta tarefa, essa é provavelmente a solução mais correta. Mas e se precisarmos substituir const por var apenas no escopo global? Mas não as substitua dentro de funções? Você pode, é claro, criar uma regularidade mais complexa ou escrever código complicado, mas existe uma maneira mais escalável e flexível.

Podemos usar os analisadores para obter o AST - Abstract Syntax Tree. Se você estiver interessado no que é o AST, acesse astexplorer.net . Em essência, é uma árvore que exibe com precisão a estrutura do seu código.

Além disso, cada um dos nós neste AST tem um inícioe índices finais indicando as posições desses elementos no código-fonte. Conhecendo essas coordenadas e tendo em mãos a estrutura do documento, podemos fazer substituições e permutações complexas com a preservação da estrutura do documento.



Geralmente, a substituição é feita usando o design do padrão de visitante e vários auxiliares , que geralmente se envolvem em uma única biblioteca, que pode ser chamada de "API do transformador". Cada analisador possui sua própria "API do transformador".

Essas bibliotecas são muito fáceis de usar, mas têm vários problemas. Um deles é o desempenho.

Como cada nó (quase todos) na árvore AST contém coordenadas, ao alterar um nó, geralmente precisamos atualizar as coordenadas para o restante da árvore. Aqui você pode argumentar que pode fazer um pouco de sangue - não atualize as coordenadas em todos os lugares, mas simplesmente retorne o AST ao texto com base na estrutura. Mas há um problema: você perderá imediatamente a formatação do texto original, o que contradiz nossa tarefa - substituir const por var na linha existente. De fato, temos uma nova linha com uma nova formatação. E se isso não for um problema para uma linha pequena, imagine um arquivo de 1000 linhas em que a formatação foi completamente alterada devido à substituição de const por var . Isso não parece muito bom.



E aqui vem a magia do MagicString. Soube pela primeira vez de sua existência no projeto Rich Harris, chamado butternut . Butternut é um minificador de JavaScript. Butternutt foi declarado 3 vezes mais rápido que o UglifyJS e 10-15 vezes mais rápido que Babili . Vou seguir em frente e dizer que o projeto foi coberto com uma bacia de cobre há pelo menos três anos. Mas, mesmo assim, fiquei intrigado com o segredo de seu desempenho. Foi um MagicString.

Vamos dar uma olhada no trabalho com o 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;'

//  

O algoritmo do MagicString é muito simples: envolvemos a string original em um objeto no qual não aplicamos diretamente as alterações na string, mas adicionamos as coordenadas e o que precisa ser feito em uma matriz para o futuro. E somente quando alguém deseja obter a linha resultante, começamos 1 a 1 para executar as operações acumuladas. Por exemplo:

  1. Substituímos const por var, começando no índice 0 e terminando no índice 5
  2. Sabemos que todas as substituições subsequentes devem ter um índice menor que 2 (var menor que const por 2 caracteres, uma linha menor)
  3. Atualizamos as coordenadas de todas as operações
  4. Aplicamos a seguinte operação, etc.




Tudo parece bem simples. Mas por que o MagicString é mais rápido? A resposta é bem simples: o número de operações que realizamos em nossa árvore é muito menor que o número de nós AST. Sem mencionar a quantidade de memória necessária para o AST e o fato de o Tree Traversal (viajar através de uma árvore) não ser uma operação livre, mas O (n + m)



E se eu estiver pronto para esperar mais meia hora? E aqui vem a segunda vantagem do MagicString. Cada analisador inventa sua própria API para transformação. E isso ainda é muito bom, se existe uma API (nem todo analisador fornece), muitas vezes ficamos sem a capacidade de substituir normalmente o texto de origem usando o AST. Mas o MagicString é uma API universal única para alterar a string de origem. Não importa qual analisador ou combinação de analisadores você usou. Com o MagicString, você pode trabalhar igualmente bem com qualquer AST.



Espero que você esteja interessado em MagicString. No próximo artigo, falarei sobre o MagicString duplo e como fazer um tradutor universal de documentos Markdown.

Inscreva-se no meu canal Telegram @obenjiro_notes e no Twitter obenjiropara não perder os seguintes artigos sobre o tema e muitas outras coisas interessantes.

All Articles