Обзор статьи
В основном он включает в себя: как перекодировать с помощью 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.
- Не знаете спецификацию ECMA: MemberExpression, FunctionDeclaration, Identifier и т. д. — все это термины в спецификации.Если у вас нет определенного понимания спецификации, вы не знаете, с чего начать при преобразовании кода. Читателям рекомендуется иметь некоторое представление о спецификации ECMA.
- Не знаю API Babel: документов API, связанных с Babel, относительно немного, что вызовет много трудностей при написании плагинов.В настоящее время лучшим решением является обращение к существующим плагинам для модификации.
В общем, читайте больше, пишите больше и проверяйте больше.
Вот еще небольшой вопрос.Предыдущий плагин заменил process.env.NODE_ENV.Если это следующий код,то как его заменить?
process.env['NODE_' + 'ENV'];