Руководство для начинающих по разработке плагинов Babel

Node.js внешний интерфейс Babel Открытый исходный код
Руководство для начинающих по разработке плагинов Babel

Обзор статьи

В основном он включает в себя: как перекодировать с помощью Babel, основы написания плагинов и примеры, объясняющие, как писать плагины.

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

Все примеры в этой статье можно найти по адресуГитхаб авторанашел, добро пожаловать в гостиблог автораПолучить больше статей по теме.

Фаза запуска Вавилона

Во-первых, давайте поймем, что процесс транскодирования Babel делится на три этапа: анализ (parse), преобразование (transform) и генерация (generate).

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

анализировать

Babel читает исходный код и после лексического и синтаксического анализа генерируетАбстрактное синтаксическое дерево (AST).

parse(sourceCode) => AST

конвертировать

После предыдущего этапа анализа кода Babel получил AST. На основе исходного AST Babel модифицирует его с помощью подключаемых модулей, таких как добавление, удаление и модификация для получения нового AST.

transform(AST, BabelPlugins) => newAST

генерировать

Через преобразование предыдущего этапа Babel получает новый AST, а затем может отменить операцию для генерации нового кода.

generate(newAST) => newSourceCode

Начало работы с основами плагина

Типичная структура плагина Babel показана в следующем коде.

export default function({ types: babelTypes }) {
  return {
    visitor: {
      Identifier(path, state) {},
      ASTNodeTypeHere(path, state) {}
    }
  };
};

Вещи, которые обращают внимание на следующие:

  • babelType: набор инструментов, такой как lodash, в основном используемый для управления узлами AST, таких как создание, проверка, преобразование и т. д. Пример: определить, является ли узел идентификатором.
  • путь: в AST много узлов, каждый узел может иметь разные свойства, и между узлами могут быть ассоциации. path — это объект, представляющий связь между двумя узлами. Вы можете получить доступ к свойствам узла на пути, а также получить доступ к связанным узлам (таким как родительские узлы, одноуровневые узлы и т. д.) через путь.
  • состояние: представляет состояние плагина, вы можете получить доступ к элементам конфигурации плагина через состояние.
  • посетитель: Babel посещает каждый узел AST рекурсивным способом Причина, по которой он называется посетителем, заключается в том, что существует аналогичный шаблон проектирования, называемыйшаблон посетителя, не заботьтесь о деталях, стоящих за ним.
  • Идентификатор, ASTNodeTypeHere: каждый узел AST имеет соответствующий тип узла, такой как идентификатор (Identifier), объявление функции (FunctionDeclaration) и т. д., и атрибут с тем же именем может быть объявлен посетителю. к соответствующему типу узла будет вызываться атрибут Соответствующий метод, а входящими параметрами являются путь и состояние.

Пример минималистского плагина

В этом примере мы реализуем бессмысленный плагин: преобразовать все идентификаторы с именем плохие в хорошие.полный код здесь.

Сначала установите зависимости проекта.

npm init -f
npm install --save-dev babel-cli

Далее создайте плагин. Определите, является ли имя идентификатора плохим, и если да, замените его на хорошее.

// plugin.js
module.exports = function({ types: babelTypes }) {
  return {
    name: "deadly-simple-plugin-example",
    visitor: {
      Identifier(path, state) {
        if (path.node.name === 'bad') {
          path.node.name = 'good';
        }
      }
    }
  };
};

Исходный код перед исходным кодом:

// index.js
let bad = true;

Запустите команду транскодирования:

npx babel --plugins ./plugin.js index.js

Выходной результат транскодирования:

// index.js
let good = true;

Конфигурация плагина

Плагины могут иметь свои собственные элементы конфигурации. Давайте изменим предыдущий пример, чтобы увидеть, как получить элементы конфигурации в плагине Babel.полный код здесь

Во-первых, мы создаем новый .babelrc и передаем элементы конфигурации.

{
  "plugins": [ ["./plugin", {
    "bad": "good",
    "dead": "alive"
  }] ]
}

Затем измените код плагина. Мы получаем параметры конфигурации из state.opts.

// plugin.js
module.exports = function({ types: babelTypes }) {
  return {
    name: "deadly-simple-plugin-example",
    visitor: {
      Identifier(path, state) {
        let name = path.node.name;
        if (state.opts[name]) {
          path.node.name = state.opts[name];
        }
      }
    }
  };
};

Измените код, который необходимо преобразовать:

// index.js
let bad = true;
let dead = true;

Запустите команду транскодированияnpx babel index.js, результат перекодирования выглядит следующим образом:

// index.js
let good = true;
let alive = true;

Пример сложного плагина: заменить process.env.NODE_ENV

Далее рассмотрим чуть более сложный, но практичный пример: замените process.env.NODE_ENV. Пример полного кода можно найти по адресунашел здесь,Ссылкаэтот плагин.

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

// index.js
if ( process.env.NODE_ENV === 'development' ) {
  console.log('我是程序猿小卡');
}

Затем мы создаем плагин с именем node-env-replacer, Код выглядит следующим образом, Код плагина будет объяснен ниже.

// plugin.js
module.exports = function({ types: babelTypes }) {
  return {
    name: "node-env-replacer",
    visitor: {
	  // 成员表达式
      MemberExpression(path, state) {
	    // 如果 object 对应的节点匹配了模式 "process.env"
        if (path.get("object").matchesPattern("process.env")) {
		  // 这里返回结果为字符串字面量类型的节点
          const key = path.toComputedKey();
          if ( babelTypes.isStringLiteral(key) ) {
		    // path.replaceWith( newNode ) 用来替换当前节点
			// babelTypes.valueToNode( value ) 用来创建节点,如果value是字符串,则返回字符串字面量类型的节点
            path.replaceWith(babelTypes.valueToNode(process.env[key.value]));
          }
        }
      }
    }
  };
};

Объяснение кода плагина

На этот раз мы имеем дело сMemberExpression. Для MemberExpression, BabelTypeопределениеследующим образом:

MemberExpression в основном состоит из объекта, свойства, вычисляемого, необязательного. В этом примере объект — это узел, соответствующий process.env, а свойство — это узел, соответствующий NODE_ENV.

defineType("MemberExpression", {
  builder: ["object", "property", "computed", "optional"],
  visitor: ["object", "property"],
  // ...
});

Как упоминалось ранее, путь соответствует атрибутам узла и отношениям между узлами. path.get("object") получает экземпляр пути, соответствующий объекту (process.env).

matchPattern(pattern) Проверяет, соответствует ли узел шаблону. В этом примере path.get("object").matchesPattern("process.env") проверяет, соответствует ли объект шаблону "process.env" . Например, выражение-член process.env.NODE_ENV имеет значение true, а выражение-член process.hello.NODE_ENV возвращает значение false.

 if (path.get("object").matchesPattern("process.env")) { }

Затем получите ключ выражения члена с помощью path.toComputedKey(), а для MemberExpression он возвращает узел типа stringLiteral.

const key = path.toComputedKey();

if ( babelTypes.isStringLiteral(key) ) определяет, является ли ключ строковым литералом, и если да, то возвращает true.

Метод path.replaceWith(node) используется для замены узла. babelTypes.valueToNode( value ) используется для создания узла. Если value является строкой, он возвращает узел строкового литерала типа.

path.replaceWith(babelTypes.valueToNode(process.env[key.value]));

Запустите плагин

Команда выглядит следующим образом:

npx babel --plugins ./plugin.js index.js

Результат преобразования:

// index.js
if ('development' === 'development') {
  console.log('我是程序猿小卡');
}

резюме

Начать работу с плагинами Babel относительно просто, просто следуйте за тыквой и рисуйте совок. В процессе написания плагинов основные препятствия, с которыми можно столкнуться, включают непонимание спецификации ECMA и непонимание API Babel.

  1. Не знаете спецификацию ECMA: MemberExpression, FunctionDeclaration, Identifier и т. д. — все это термины в спецификации.Если у вас нет определенного понимания спецификации, вы не знаете, с чего начать при преобразовании кода. Читателям рекомендуется иметь некоторое представление о спецификации ECMA.
  2. Не знаю API Babel: документов API, связанных с Babel, относительно немного, что вызовет много трудностей при написании плагинов.В настоящее время лучшим решением является обращение к существующим плагинам для модификации.

В общем, читайте больше, пишите больше и проверяйте больше.

Вот еще небольшой вопрос.Предыдущий плагин заменил process.env.NODE_ENV.Если это следующий код,то как его заменить?

process.env['NODE_' + 'ENV'];

Ссылки по теме

babel-handbook

ECMA-262/5.1

Документация к основному пакету Babel

Документация, предоставленная энтузиастами-разработчиками