Представьте: вы находитесь в модном ресторане, где меню написано на языке будущего. Вы голодны прямо сейчас, но повар говорит только на диалекте вчерашнего дня. Вступите в игру Babel — космический переводчик, который превращает ваш футуристический JavaScript в нечто, что понял бы даже IE6 (если бы он не был, ну, мёртв). Но что если вы хотите изобрести свой собственный кулинарный синтаксис? Вот тут-то и приходит на помощь волшебство плагинов. Берите лопату для теста, мы займёмся преобразованием AST!

Зачем создавать собственный плагин Babel?

Когда я впервые попробовал себя в разработке плагинов, я спросил: «Почему бы не использовать существующие инструменты?» Затем я попытался написать leftPad() в 37-й раз и понял: настоящая сила заключается в изобретении собственных абстракций. Плагины позволяют вам:

  • Приспосабливать синтаксис JavaScript под свои нужды (например, добавлять операторы конвейера 👀);
  • Автоматизировать утомительные шаблоны (прощайте, повторяющиеся проверки ошибок);
  • Экспериментировать с функциями языка до их одобрения TC39;
  • Путать коллег (шутка… в основном).

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

Настройка вашей мастерской плагинов

Прежде чем мы призовём демонов синтаксиса, давайте настроим наш котёл:

  1. Установите предварительные требования:
npm install --save-dev @babel/core @babel/cli
  1. Создайте каркас вашего плагина (мой называется babel-plugin-disappear):
// disappear-plugin.js
export default function ({ types: t }) {
  return {
    visitor: {
      // Здесь скоро произойдёт наше волшебство...
    }
  };
}
  1. Конфигурация для пробного запуска (.babelrc):
{
  "plugins": ["./disappear-plugin.js"]
}

Совет: назовите свой плагин как-нибудь драматично. Я чуть было не назвал свой babel-plugin-thanos, но решил, что исчезновение 50% вашего кода может быть слишком буквально.

AST: Универсальный ключ к вашему коду

Вот где всё становится потрясающе. Весь код — это просто Абстрактные Синтаксические Деревья — вложенные объекты, описывающие каждую запятую, скобку и точку с запятой. Взгляните на AST для foo === bar:

graph TD A[BinaryExpression] --> B[Identifier: foo] A --> C[Operator: ===] A --> D[Identifier: bar]

Эта иерархическая структура — вот что делает плагины Babel похожими на проведение нейрохирургии с помощью садовых ножниц. Вы не редактируете текст — вы переписываете реальность на уровне концепций.

Создание нашего плагина «Исчезающий»

Давайте создадим плагин, который будет удалять операторы console.log во время сборки — идеально для тех моментов, когда вы случайно оставили отладчики в продакшене.

Шаг 1: Определите свою цель

Мы хотим найти все вызовы console.log. Их AST выглядит так:

{
  type: "CallExpression",
  callee: {
    type: "MemberExpression",
    object: { type: "Identifier", name: "console" },
    property: { type: "Identifier", name: "log" }
  }
}

Шаг 2: Шаблон посетителя

Плагины работают, проходя по AST и реагируя на типы узлов, как чрезмерно усердные охранники:

visitor: {
  CallExpression(path) {
    if (
      t.isMemberExpression(path.node.callee) &&
      t.isIdentifier(path.node.callee.object, { name: "console" }) &&
      t.isIdentifier(path.node.callee.property, { name: "log" })
    ) {
      // Уничтожить этот узел!
      path.remove();
    }
  }
}

Шаг 3: Повышение уровня с помощью опций

Захардкоженные поведения остались в 2015 году. Давайте сделаем наш плагин настраиваемым:

export default function ({ types: t }) {
  return {
    visitor: {
      CallExpression(path, state) {
        const targets = state.opts.targets || ['log'];
        // Проверка по настроенным целям
      }
    }
  };
}

Теперь пользователи могут удалять warn или error тоже:

{
  "plugins": [
    ["./disappear-plugin.js", { "targets": ["log", "warn"] }]
  ]
}

Тестирование: Не выпускайте голодного Годзиллу

Всегда тестируйте свои монстры, преобразующие AST, если не хотите ломать продакшен. Попробуйте @babel/parser и @babel/traverse:

import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
const code = `console.log("Oops")`;
const ast = parse(code);
traverse(ast, {
  CallExpression: pluginVisitor // Ваша логика посетителя здесь
});
// Должно быть пусто!
console.log(ast.program.body); 

Для реальных проектов используйте babel-plugin-tester — это как модульные тесты для вашего искривления реальности:

import pluginTester from 'babel-plugin-tester';
import plugin from '../disappear-plugin';
pluginTester({
  plugin,
  tests: {
    'disappears console.log': {
      code: 'console.log("vanish")',
      output: '' // Ожидаемый пустой вывод
    }
  }
});

Продвинутое колдовство

Как только вы освоите основы, попробуйте эти мощные приёмы:

Манипуляция путями

Меняйте переменные как мошенник:

BinaryExpression(path) {
  if (path.node.operator === "===") {
    // Поменяйте местами left/right как котлеты для бургера
    const { left, right } = path.node;
    path.node.left = right;
    path.node.right = left;
  }
}

Вход: foo === bar → Выход: bar === foo
Хаос достигнут.

Генерация кода с нуля

Когда вам нужно внедрить код как инфузию синтаксиса:

FunctionDeclaration(path) {
  // Добавить в начало "console.error('Here be dragons')"
  path.get('body').unshiftContainer(
    'body',
    t.expressionStatement(
      t.callExpression(
        t.memberExpression(
          t.identifier('console'),
          t.identifier('error')
        ),
        [t.stringLiteral('Here be dragons')]
      )
    )
  );
}

Когда на самом деле стоит создать плагин?

Серьёзный разговор: Не каждая проблема требует решения с помощью AST. Но когда вы видите повторяющиеся шаблоны, которые можно сделать чище на уровне синтаксиса? Это ваш золотой билет. Вот несколько законных случаев использования:

  • Доменно-специфичные языки (например, пользовательские построители запросов);
  • Автоматическая инструментация производительности;
  • Переключатели поведения для разработки и продакшена;
  • Навязывание архитектурных паттернов (например, все сетевые вызовы через шлюз).

Просто помните: с великой силой приходит великая ответственность не создавать плагин, который заменяет все точки с запятой на эмодзи. (Если только это не пятница. Тогда можно.)

Полная шпаргалка

graph LR A[Идея] --> B(Инспекция AST) B --> C(Шаблон посетителя) C --> D(Манипуляция путями) D --> E(Обработка опций) E --> F(Тестирование) F --> G[Покорение мира]

Так что в следующий раз, когда вы будете проклинать конфигурацию Babel, помните: вы не настраиваете инструмент — вы держите генератор синтаксических червоточин. Теперь идите и создайте плагин, который заменяет undefined на ¯\_(ツ)_/¯ — я верю в вас!