Когда вы слышите абстрактные синтаксические деревья, какая первая мысль приходит вам в голову?
Что-то делать с компиляторами? Какие-то сложные манипуляции с деревом? Битовые манипуляции? 🤔

В начале моей карьеры этот AST казался сложным термином, в который добавлялась магия компиляторов низкого уровня и транспиляторов.

💡 Мотивация

Мотивация написания этого блога - помочь каждому понять, что такое абстрактные синтаксические деревья и как они играют важную роль в большинстве инструментов, которые мы используем ежедневно.

Будь то babel, webpack, parcel, eslint, codemods, css parsers, css in js - все эти инструменты используют магию AST для управления нашим кодом и преобразования его во что-то еще.

В этом посте мы раскроем эту магию и в процессе научимся писать несколько супер простых плагинов babel. Да

🤔 Что такое AST?

Как и любую новую концепцию, мы начнем с конкретного определения.
Согласно википедии

Абстрактное синтаксическое дерево - это древовидное представление исходного кода, написанного на языке программирования. Каждый узел дерева обозначает конструкцию, встречающуюся в исходном коде.

Чтобы понять это, представьте, что мы пишем простую строку кода в нашем редакторе.

Это очень простое присвоение переменной и сложение двух чисел.
Эта простая операция проходит через процесс Tokenization и Parsing.

🕹️ Токенизация (лексический анализ)

Токенизация или лексический анализ - это этап, на котором функция считывает код как строку и разбивает ее на список токенов.

Для простоты предположим, что каждый токен имеет следующий интерфейс

Наш код проходит процесс лексического анализа и разбивается на токены.

🧵 Парсинг (синтаксический анализ)

Постлексический анализ дает нам массив токенов, который мы пропускаем через синтаксический анализатор AST (babylon, acorn или espree), который преобразует его в дерево узлов AST, устанавливая зависимости между ними.

Сверхпростой код, который мы написали, преобразуется в дерево узлов, которое мы называем абстрактным синтаксическим деревом.

И все это дерево представлено как json следующим образом

В этом объекте json мы замечаем параметр с именем type. Мы называем их типами узлов AST.
Существует несколько типов узлов AST, и для babel мы можем ссылаться на следующие
Типы узлов Babel AST

Для парсера espree (который использует eslint) мы можем сослаться здесь Типы узлов Eslint AST

Babel, webpack, parcel и все эти инструменты используют общий подход.
Сначала они преобразуют наш код в дерево AST, затем применяют к нему некоторые преобразования (добавляют, редактируют, обновляют, удаляют), создают новое дерево из этих преобразований, а затем преобразуют его обратно в код, читаемый человеком.

Чтобы понять, как будет выглядеть представление AST-дерева конкретной строки кода, я бы рекомендовал всегда проверять AST Explorer

Теперь, не теряя времени, мы напишем наш самый первый плагин babel.
Этот плагин удалит все операторы отладчика, которые могут быть в нашем коде.

📕 Плагин Babel - удаление отладчика

Подумайте о том, чтобы следующий код был в нескольких местах вашего репо

Совершенно очевидно, что мы не хотели бы, чтобы эти операторы отладчика попадали в наше производственное приложение.
(Примечание: в реальном приложении у нас был бы какой-то сборщик или какой-то этап конвейера развертывания, который может помочь нам избежать таких ошибок. , но для этого примера предположим, что у нас нет такого конвейера развертывания).

Поэтому мы пишем плагин babel, чтобы сделать то же самое для нас.

Написание плагина babel

Шаг 1: определите тип узла AST, на который мы хотим настроить таргетинг. Если мы перейдем в AST Explorer и щелкнем по строке 2, мы заметим, что тип узла выделен желтым цветом, и это показывает нам, что узел AST, на который мы должны нацелить, - это DebuggerStatement.

Шаг 2. Запустите редактор и создайте новый файл. Назовем его removeDebugger.js. Это будет наш файл плагина.

Каждый плагин babel, который мы пишем с этого момента, будет следовать общему шаблону

Мы возвращаем объект с другим вложенным объектом с ключом visitor в нем. Он назван посетителем из-за шаблона посетителя.

Шаг 3. Мы знаем, что тип узла, на который мы хотим настроить таргетинг, - DebuggerStatement.

Теперь наш код будет выглядеть так

Каждый узел, на который мы хотим настроить таргетинг, должен быть ключом внутри нашего объекта посетителя.

Шаг 4: теперь единственный шаг, который остается в этом плагине babel, - это удалить узел оператора отладчика, и мы делаем это следующим образом:

И что мои друзья были нашим первым плагином Babel.

Этот плагин babel объясняет нам, как управлять AST, удаляя из него узел.

Следующий плагин, который мы изучим, объяснит нам, как редактировать существующий узел и преобразовывать его во что-то еще.

📕 Плагин Babel - предупреждение для консоли

Таким образом, здесь мы преобразуем каждый оператор alert в оператор console.warn.

Итак, наш код, который мы хотим изменить, будет выглядеть примерно так

Мы преобразуем это в

Шаг 1: определите тип узла AST, на который мы хотим настроить таргетинг. Идем в проводник AST, копируем, вставляем наш from код и нажимаем alert. Он выделит тип узла справа. Мы видим, что теперь целевой тип узла называется CallExpression.

Таким образом, любой вызов функции - это CallExpression, а любой вызов функции объекта называется MemberExpression. Итак, alert это CallExpression, а console.warn isMemberExpression.

В нашем случае выражение MemberExpression всегда будет иметь объект (консоль) и свойство (предупреждение).

Шаг 2: снова запустите редактор и создайте новый файл. Назовем его convertAlertToConsole.js.

Как и до того, как мы запустим наш плагин со скелетным кодом

Шаг 3. Теперь, когда мы знаем, что целевой узел - это CallExpression, давайте напишем наш код

Шаг 4. Поскольку мы не хотим нацеливаться на вызов всех остальных функций, давайте поместим условие if, чтобы указать, что мы хотим нацеливаться только на выражение вызова с именем alert

Теперь остается только выяснить, чем его заменить.

Шаг 5. Мы возвращаемся в проводник AST и на этот раз копируем наш to код, и нажатие на console.warn сообщит нам, что нам нужно заменить его другим выражением вызова, поскольку все вызовы функций являются выражениями вызова, но поскольку это object property function call, поэтому ему нужно выражение вызова с выражением члена внутри него, поскольку оно вызывается.

И это все. Мы написали и наш второй плагин 🥳. Разве все это не слишком просто? 🤗

📕 Бонусный плагин - удалить data-test-id из приложения реакции

В этом случае мы удалим все атрибуты data-test-id из нашего приложения реакции. Поскольку data-test-id обычно требуется только в среде разработки, наш плагин может безопасно удалить это из нашего производственного пакета.

Итак, наш код, который мы хотим изменить, будет выглядеть примерно так

Мы преобразуем это в

Шаг 1: определите тип узла AST, на который мы хотим настроить таргетинг. Идем в проводник AST и копируем, вставляем наш from код и нажимаем data-test-id. Он выделит тип узла справа. Мы видим, что теперь целевой тип узла называется JSXAttribute.

Шаг 2. Запустите редактор и создайте новый файл. Назовем его removeDataAttribute.js.
Как и до того, как мы запустим наш плагин со скелетным кодом

Шаг 3. Теперь, когда мы знаем, что целевой узел - это JSXAttribute, давайте напишем наш код

Теперь наш код будет выглядеть так

Шаг 4: теперь единственный шаг, который остается в этом плагине babel, - это удалить этот узел атрибута jsx, и мы делаем это следующим образом:

И это все. Мы написали еще один плагин 🥳.

Репозиторий Github: https://github.com/vivek12345/webcamp-zagreb-demo

🍬 Заключение

Я надеюсь, что это поможет нам всем понять, что AST не сложны, и что каждый из нас может улучшить нашу экосистему инструментов для разработчиков, создав плагины для линтера, или написав плагины babel, или выполнив крупномасштабные рефакторы с использованием кодовых модификаций. И если у вас есть настроение, вы также можете написать css в библиотеке js.

🔗 Ссылки