Праздник Дня закончился, а остатки времени я все еще трачу на написание статей. Не знаю, прочтет ли кто-нибудь во время длинных праздников. Попробуем!
Эта серия статей проведет вас черезBabel
, Эта серия будет разделена на две части: первая часть в основном знакомит с архитектурой и принципами Babel, и, кстати, практикует разработку плагинов; следующая часть познакомит`babel-plugin-macros , используйте его для написания «макроса», принадлежащего Javascript,
✨Полон галантерейных товаров, не пропустите. Писать не легко, это самое большое поощрение.
Примечание. Эта статья не является базовым руководством по использованию Babel! Если вы еще не знакомы с Babel, посмотритеОфициальный сайт, или этоРуководство пользователя
Следующая статья была обновлена:Объясните простыми словами Babel Часть 2: Плагин и макросыНемного пустынно, мне нравится. Добро пожаловать в перепечатку, пусть больше людей увидит мою статью, пожалуйста, укажите источник
Схема статьи
Поток обработки Babel
Поток обработки BabelВышеприведенное изображение представляет собой поток обработки Babel, если читатель узнал编译器原理
, процесс довольно дружелюбный.
Сначала из исходного кода解析(Parsing)
Первоначально парсинг состоит из двух шагов:
1️⃣Лексический анализ:词法解析器(Tokenizer)
На этом этапе код в строковой форме преобразуется вTokens(令牌)
Токены можно рассматривать как массив фрагментов синтаксиса, напримерfor (const item of items) {}
Результат после лексического анализа следующий:
Как видно из рисунка выше, каждый токен содержит фрагменты синтаксиса, информацию о местоположении и некоторую информацию о типе, которая полезна для последующего анализа синтаксиса.
2️⃣syntactic анализ: Эта стадия грамматики解析器(Parser)
положитTokens
Перевести в抽象语法树(Abstract Syntax Tree,AST)
что такое АСТ?
Это «дерево объектов», представляющее синтаксическую структуру кода, напримерconsole.log('hello world')
будет разбирать как:
Program
,CallExpression
,Identifier
Это типы узлов, каждый узел представляет собой значимую синтаксическую единицу.. Эти типы узлов определяют некоторые свойства для описания информации об узле.
Синтаксис JavaScript становится все более и более сложным, и в дополнение к поддержке последнего синтаксиса спецификации JavaScript, Babel также поддерживаетJSX
,Flow
, а сейчас естьTypescript
. Представьте, сколько типов узлов в AST, на самом деле нам не нужно запоминать столько типов, и мы их не запоминаем.Разработчики плагинов будут использоватьASTExplorer
изучить проанализированное дерево AST, очень мощный 👍.
AST — это основная структура данных перевода Babel, и последующие операции зависят от AST..
Затем следует **Преобразование**, фаза преобразования будет проходить через AST и добавлять, удалять, проверять и изменять узлы в процессе. На этом этапе работают все плагины Babel, например, преобразование синтаксиса и сжатие кода.
Javascript In Javascript Out, Заключительный этап заключается в преобразовании AST обратно в Javascript в виде строки, и на этом этапе также создается исходная карта.
Архитектура Вавилона
я здесь«Видеть суть через феномен: общие стили и кейсы архитектуры интерфейса🔥»упомянутьBabel
а такжеWebpack
Чтобы адаптироваться к сложным требованиям настройки и частым функциональным изменениям, оба используютмикроядроархитектурный стиль.То есть их ядро очень маленькое, а большинство функций реализовано через плагины-расширения..
Поэтому полезно иметь краткое представление об архитектуре Babel и некоторых основных концепциях, чтобы понять содержание последующих статей и использовать Babel.
Одна картинка стоит тысячи слов. Друзья, внимательно прочитавшие мои статьи, обнаружат, что мой стиль заключается в том, что если вы можете использовать изображения, вам не нужны слова, а если вы можете использовать слова, вам не нужен код.Хотя моя исходная статья очень длинная, картинки все равно стоит посмотреть.
Бабель - этоMonoRepo
Проект, но организация очень ясна, ниже приведена классификация модулей, которые мы можем видеть в исходном коде, с приведенной выше архитектурной диаграммой, чтобы дать вам общее представление о Babel:
1️⃣ Ядро:
@babel/core
Это также «ядро» в архитектуре «микроядра», упомянутой выше. Для Babel это ядро в основном делает следующие вещи:
- Загрузка и обработка конфигурации (config)
- Загрузить плагин
- передача
Parser
разбор, генерацияAST
- передача
Traverser
Пройдите AST и используйте访问者模式
Примените «плагин» для преобразования AST - Создание кода, включая преобразование SourceMap и генерацию исходного кода
2️⃣ Поддержка по всему телу
-
Parser(
@babel/parser
): Все дело в парсинге исходного кода в AST. Он имеет встроенную поддержку многих синтаксисов, например, JSX, Typescript, Flow и последнюю спецификацию ECMAScript. В настоящее время для повышения эффективности работы синтаксический анализаторрасширение не поддерживается, поддерживаемый чиновником. Если вы хотите поддерживать пользовательский синтаксис, вы можете разветвить его, но это очень редко. -
Traverser(
@babel/traverse
): Достигнуто访问者模式
, пройти AST,转换插件
Через него он получит интересующий узел AST и продолжит работу на узле, что будет подробно описано ниже.访问器模式
. -
Generator(
@babel/generator
): преобразование AST в исходный код, поддержка SourceMap
3️⃣ Плагины
Откройте исходный код Babel, и вы найдете несколько типов «плагинов».
-
синтаксический плагин (
@babel/plugin-syntax-*
): сказано выше@babel/parser
Многие функции синтаксиса JavaScript уже поддерживаются, а Parser не поддерживает расширения.следовательноplugin-syntax-*
На самом деле он используется только для включения или настройки определенной функции Parser..Обычным пользователям не нужно заботиться об этом, плагин Transform уже содержит соответствующие
plugin-syntax-*
плагин тоже. Пользователи также могутparserOpts
Элементы конфигурации для непосредственной настройки Parser -
плагин преобразования: Используется для преобразования AST для преобразования в код ES5, сжатия, улучшения функций и т. д. Репозиторий Babel делит плагины преобразования на два типа (разница только в названиях):
-
@babel/plugin-transform-*
: обычный плагин преобразования -
@babel/plugin-proposal-*
: языковые функции, которые все еще находятся на «стадии предложения» (неформальные), в настоящее времяЭти
-
-
Предопределенные коллекции (
@babel/presets-*
): Коллекция или группировка плагинов, что в основном удобно пользователям для управления и использования плагинов. Напримерpreset-env
Включите все последние функции стандарта, напримерpreset-react
Включает в себя все плагины, связанные с реакцией.
4️⃣ Помощь в разработке плагинов
-
@babel/template
: В некоторых сценариях слишком сложно напрямую манипулировать AST, точно так же, как мы напрямую манипулируем DOM, поэтому Babel реализует такой простой механизм шаблонов, который может преобразовывать строковый код в AST. Например, эта библиотека используется при генерации некоторого вспомогательного кода (помощника) -
@babel/types
: конструкторы и утверждения узлов AST. Часто используется при разработке плагинов. -
@babel/helper-*
: некоторые помощники для помощи в разработке плагинов, такие как упрощение операций AST. -
@babel/helper
: Вспомогательный код, простое преобразование синтаксиса может не заставить код работать. Например, браузеры с низкими версиями не могут распознать ключевое слово class. В настоящее время необходимо добавить вспомогательный код для имитации класса.
5️⃣ Инструменты
-
@babel/node
: Node.js CLI, через который можно напрямую запускать файлы JavaScript, которые должны быть обработаны Babel. -
@babel/register
: метод require Patch NodeJs, который поддерживает импорт модулей JavaScript, которые должны быть обработаны Babel. -
@babel/cli
: Инструменты командной строки
шаблон посетителя
Преобразователь пройдёт по дереву AST, найдёт интересующий его тип узла, а затем выполнит операцию преобразования, аналогичную нашей операции.DOM
Деревья похожи, но назначение разное. Обходы и преобразования AST обычно используют访问者模式
.
Представьте, что у Babel так много плагинов, если каждый плагин сам по себе проходит через AST, выполняет разные операции на разных узлах и поддерживает свое состояние. Это не только неэффективно, но и их логика разбросана повсюду, что затруднит понимание и отладку всей системы, наконец, отношения между плагинами запутаны и запутаны.
Таким образом, операция преобразования AST обычно используется访问器模式
, этим访问者(Visitor)
Чтобы ① выполнить унифицированную операцию обхода, ② предоставить метод работы узлов и ③ поддерживать взаимосвязь между узлами в режиме реагирования; в то время как подключаемый модуль (называемый «конкретный посетитель» в режиме разработки) должен только определить узел тип, который его интересует. Когда посетитель обращается к соответствующему узлу, вызывается метод посещения плагина.
обход узлов
Предположим, наш код выглядит следующим образом:
function hello(v) {
console.log('hello' + v + '!')
}
Разобранная структура AST выглядит следующим образом:
File
Program (program)
FunctionDeclaration (body)
Identifier (id) #hello
Identifier (params[0]) #v
BlockStatement (body)
ExpressionStatement ([0])
CallExpression (expression)
MemberExpression (callee) #console.log
Identifier (object) #console
Identifier (property) #log
BinaryExpression (arguments[0])
BinaryExpression (left)
StringLiteral (left) #'hello'
Identifier (right) #v
StringLiteral (right) #'!'
Посетители будут深度优先
, или рекурсивно обходить AST, а последовательность вызова показана на следующем рисунке:
На фото выше绿线
Указывает на вход в узел,红线
Указывает покинуть узел. Давайте напишем очень простой «конкретный посетитель», чтобы восстановить описанный выше процесс обхода:
const babel = require('@babel/core')
const traverse = require('@babel/traverse').default
const ast = babel.parseSync(code)
let depth = 0
traverse(ast, {
enter(path) {
console.log(`enter ${path.type}(${path.key})`)
depth++
},
exit(path) {
depth--
console.log(` exit ${path.type}(${path.key})`)
}
})
Просмотр результатов выполнения кода
enter Program(program)
enter FunctionDeclaration(0)
enter Identifier(id)
exit Identifier(id)
enter Identifier(0)
exit Identifier(0)
enter BlockStatement(body)
enter ExpressionStatement(0)
enter CallExpression(expression)
enter MemberExpression(callee)
enter Identifier(object)
exit Identifier(object)
enter Identifier(property)
exit Identifier(property)
exit MemberExpression(callee)
enter BinaryExpression(0)
enter BinaryExpression(left)
enter StringLiteral(left)
exit StringLiteral(left)
enter Identifier(right)
exit Identifier(right)
exit BinaryExpression(left)
enter StringLiteral(right)
exit StringLiteral(right)
exit BinaryExpression(0)
exit CallExpression(expression)
exit ExpressionStatement(0)
exit BlockStatement(body)
exit FunctionDeclaration(0)
exit Program(program)
Вызывается, когда посетитель входит в узелenter(进入)
метод, иначе он будет вызываться при выходе из узлаexit(离开)
метод. В обычных условиях плагин не будет использоваться напрямую.enter
метод, сосредоточьтесь только на нескольких типах узлов, поэтому определенные посетители могут также объявлять методы доступа следующим образом:
traverse(ast, {
// 访问标识符
Identifier(path) {
console.log(`enter Identifier`)
},
// 访问调用表达式
CallExpression(path) {
console.log(`enter CallExpression`)
},
// 上面是enter的简写,如果要处理exit,也可以这样
// 二元操作符
BinaryExpression: {
enter(path) {},
exit(path) {},
},
// 更高级的, 使用同一个方法访问多种类型的节点
"ExportNamedDeclaration|Flow"(path) {}
})
Так как же применяется плагин Babel?
Babel будет применять методы доступа в порядке, определенном плагинами.Например, если вы зарегистрируете несколько плагинов, структура данных, которую babel-core в конечном итоге передаст методу доступа, будет примерно такой длины:
{
Identifier: {
enter: [plugin-xx, plugin-yy,] // 数组形式
}
}
При входе в ноду эти плагины выполняются в том порядке, в котором они были зарегистрированы. Большинство плагинов не требуют от разработчиков заботы о порядке определений. Есть несколько случаев, требующих небольшого внимания, напримерplugin-proposal-decorators
:
{
"plugins": [
"@babel/plugin-proposal-decorators", // 必须在plugin-proposal-class-properties之前
"@babel/plugin-proposal-class-properties"
]
}
По соглашению порядок, в котором определяются все плагины, должен быть следующим: сначала новые или экспериментальные плагины, а затем старые плагины. Потому что AST может потребоваться преобразовать новым подключаемым модулем, прежде чем старый подключаемый модуль сможет распознать синтаксис (обратная совместимость). Ниже приведен пример официальной конфигурации для обеспечения совместимости.stage-*
Фазовые плагины выполняются первыми:
{
"presets": ["es2015", "react", "stage-2"]
}
Обратите внимание, что порядок выполнения Preset обратный, см.Документация
контекст узла
Когда посетитель посещает узел, он будет звонить без разбораenter
метод, как мы узнаем, где находится этот узел и как он связан с другими узлами?
С помощью приведенного выше кода читатели должны угадать несколько точек, каждая из которыхvisit
методы получаютPath
объект, вы можете думать о нем как об объекте «контекст», что-то вродеJQuery
изJQuery
(const $el = $('.el')
) объект, который содержит много информации:
- информация о текущем узле
- Информация, связанная с узлом. родительский узел, дочерний узел, родственный узел и т. д.
- информация о области действия
- контекстная информация
- Метод работы узла. Добавление, удаление и изменение узла
- Метод утверждения. isXXX, утверждатьXXX
Вот его основная структура:
export class NodePath<T = Node> {
constructor(hub: Hub, parent: Node);
parent: Node;
hub: Hub;
contexts: TraversalContext[];
data: object;
shouldSkip: boolean;
shouldStop: boolean;
removed: boolean;
state: any;
opts: object;
skipKeys: object;
parentPath: NodePath;
context: TraversalContext;
container: object | object[];
listKey: string; // 如果节点在一个数组中,这个就是节点数组的键
inList: boolean;
parentKey: string;
key: string | number; // 节点所在的键或索引
node: T; // 🔴 当前节点
scope: Scope; // 🔴当前节点所在的作用域
type: T extends undefined | null ? string | null : string; // 🔴节点类型
typeAnnotation: object;
// ... 还有很多方法,实现增删查改
}
ты можешь пройти эторуководствоДавайте узнаем, как конвертировать AST через Path, позже будут также примеры кода, поэтому я не буду здесь подробно останавливаться.
Обработка побочных эффектов
На самом деле работа посетителя сложнее, чем мы себе представляли.Приведенная выше демонстрация — это процесс обхода статического AST. Само преобразование AST имеет побочные эффекты, например, если плагин заменяет старый узел, то посетитель не должен посещать старый узел, а продолжает посещать новый узел, код выглядит следующим образом.
traverse(ast, {
ExpressionStatement(path) {
// 将 `console.log('hello' + v + '!')` 替换为 `return ‘hello’ + v`
const rtn = t.returnStatement(t.binaryExpression('+', t.stringLiteral('hello'), t.identifier('v')))
path.replaceWith(rtn)
},
}
Приведенный выше код будетconsole.log('hello' + v + '!')
заменить заявление наreturn "hello" + v;
, на следующем рисунке показан процесс обхода:
Мы можем выполнять произвольные операции с AST, такие как удаление родственного узла родительского узла, удаление первого дочернего узла, добавление родственного узла...Когда эти операции «загрязняют» дерево AST, посетитель должен записывать эти состояния и реактивно обновлять ассоциативные отношения объекта «Путь», чтобы обеспечить правильный порядок обхода и получить правильный результат перевода..
Обработка областей
Посетители могут убедиться, что узлы пройдены и изменены правильно, но для конвертеров есть еще одна сложная часть — обработка областей видимости, которая ложится на голову разработчика плагина. Разработчики плагинов должны быть очень осторожны с областями действия и не нарушать логику выполнения существующего кода.
const a = 1, b = 2
function add(foo, bar) {
console.log(a, b)
return foo + bar
}
Например, если вы хотитеadd
первый параметр функцииfoo
Идентификатор изменен наa
, тебе нужнорекурсияПройдите по поддереву и найдитеfoo
все идентификаторы引用
, затем замените его на:
traverse(ast, {
// 将第一个参数名转换为a
FunctionDeclaration(path) {
const firstParams = path.get('params.0')
if (firstParams == null) {
return
}
const name = firstParams.node.name
// 递归遍历,这是插件常用的模式。这样可以避免影响到外部作用域
path.traverse({
Identifier(path) {
if (path.node.name === name) {
path.replaceWith(t.identifier('a'))
}
}
})
},
})
console.log(generate(ast).code)
// function add(a, bar) {
// console.log(a, b);
// return a + bar;
// }
🤯Подождите, это не кажется таким простым, замените его наa
Позже,console.log(a, b)
поведение разрушено. Так что здесь нельзя использоватьa
, вы должны изменить идентификатор, напримерc
.
Это проблема области действия, которую конвертер должен учитывать,Предпосылкой преобразования AST является обеспечение корректности программы.. Мы добавляем и изменяем引用
, необходимо убедиться, что он не конфликтует ни с какими существующими ссылками. Сам Babel не может обнаруживать такие аномалии и может полагаться только на разработчиков плагинов, которые тщательно с ними справятся.
Javascript использует лексическую область видимости, то есть область видимости определяется в соответствии с лексической структурой исходного кода:
существуетЛексический блок (блок), к этой области блока относятся идентификаторы, созданные за счет новых переменных, функций, классов, параметров функций и т. д. Эти идентификаторы также называютсяСвязывание, а использование этих привязок называетсяСсылка
В Бабеле используйтеScope
объект для представления области. Мы можем передать объект Pathscope
поле для получения текущего узлаScope
объект. Его структура выглядит следующим образом:
{
path: NodePath;
block: Node; // 所属的词法区块节点, 例如函数节点、条件语句节点
parentBlock: Node; // 所属的父级词法区块节点
parent: Scope; // ⚛️指向父作用域
bindings: { [name: string]: Binding; }; // ⚛️ 该作用域下面的所有绑定(即该作用域创建的标识符)
}
Scope
объект иPath
Объект почтиОн содержит отношения между областями (указывая на родительскую область через родительскую), собирает все привязки в пределах области (привязки), а также предоставляет богатые методы для работы только с областями..
мы можем пройтиbindings
свойство получает все привязки (т. е. идентификаторы) в текущей области, каждая привязка определяетсяBinding
класс для представления:
export class Binding {
identifier: t.Identifier;
scope: Scope;
path: NodePath;
kind: "var" | "let" | "const" | "module";
referenced: boolean;
references: number; // 被引用的数量
referencePaths: NodePath[]; // ⚛️获取所有应用该标识符的节点路径
constant: boolean; // 是否是常量
constantViolations: NodePath[];
}
пройти черезBinding
объект, мы можем определить случай, когда идентификатор ссылается.
Хорошо, естьScope
а такжеBinding
, теперь имеет возможность реализовать безопасное преобразование переименования переменных. Чтобы лучше продемонстрировать взаимодействие области действия, на основе приведенного выше кода увеличим сложность:
const a = 1, b = 2
function add(foo, bar) {
console.log(a, b)
return () => {
const a = '1' // 新增了一个变量声明
return a + (foo + bar)
}
}
Теперь вы должны переименовать параметр функцииfoo
, не только рассматривать外部的作用域
, также учитывайте下级作用域
Условия привязки, убедитесь, что они не противоречат друг другу.
Приведенная выше ситуация с областью кода и ссылкой на идентификатор показана на следующем рисунке:
Давай, прими вызов и попробуй переименовать первый аргумент функции в более короткий идентификатор:
// 用于获取唯一的标识符
const getUid = () => {
let uid = 0
return () => `_${(uid++) || ''}`
}
const ast = babel.parseSync(code)
traverse(ast, {
FunctionDeclaration(path) {
// 获取第一个参数
const firstParam = path.get('params.0')
if (firstParam == null) {
return
}
const currentName = firstParam.node.name
const currentBinding = path.scope.getBinding(currentName)
const gid = getUid()
let sname
// 循环找出没有被占用的变量名
while(true) {
sname = gid()
// 1️⃣首先看一下父作用域是否已定义了该变量
if (path.scope.parentHasBinding(sname)) {
continue
}
// 2️⃣ 检查当前作用域是否定义了变量
if (path.scope.hasOwnBinding(sname)) {
// 已占用
continue
}
// 再检查第一个参数的当前的引用情况,
// 如果它所在的作用域定义了同名的变量,我们也得放弃
if (currentBinding.references > 0) {
let findIt = false
for (const refNode of currentBinding.referencePaths) {
if (refNode.scope !== path.scope && refNode.scope.hasBinding(sname)) {
findIt = true
break
}
}
if (findIt) {
continue
}
}
break
}
// 开始替换掉
const i = t.identifier(sname)
currentBinding.referencePaths.forEach(p => p.replaceWith(i))
firstParam.replaceWith(i)
},
})
console.log(generate(ast).code)
// const a = 1,
// b = 2;
// function add(_, bar) {
// console.log(a, b);
// return () => {
// const a = '1'; // 新增了一个变量声明
// return a + (_ + bar);
// };
// }
Хотя приведенный выше пример не имеет практического применения, и все же есть баги (не учтенныеlabel
), а просто для того, чтобы показать сложность работы с областью видимости.
ВаритьScope
Объект фактически предоставляетgenerateUid
метод для создания уникальных, неконфликтующих идентификаторов. Давайте воспользуемся этим методом, чтобы снова упростить наш код:
traverse(ast, {
FunctionDeclaration(path) {
const firstParam = path.get('params.0')
if (firstParam == null) {
return
}
let i = path.scope.generateUidIdentifier('_') // 也可以使用generateUid
const currentBinding = path.scope.getBinding(firstParam.node.name)
currentBinding.referencePaths.forEach(p => p.replaceWith(i))
firstParam.replaceWith(i)
},
})
Можно ли покороче!
traverse(ast, {
FunctionDeclaration(path) {
const firstParam = path.get('params.0')
if (firstParam == null) {
return
}
let i = path.scope.generateUid('_') // 也可以使用generateUid
path.scope.rename(firstParam.node.name, i)
},
})
Посмотреть код реализации generateUid
generateUid(name: string = "temp") {
name = t
.toIdentifier(name)
.replace(/^_+/, "")
.replace(/[0-9]+$/g, "");
let uid;
let i = 0;
do {
uid = this._generateUid(name, i);
i++;
} while (
this.hasLabel(uid) ||
this.hasBinding(uid) ||
this.hasGlobal(uid) ||
this.hasReference(uid)
);
const program = this.getProgramParent();
program.references[uid] = true;
program.uids[uid] = true;
return uid;
}
Очень лаконично да? Наиболее типичным сценарием работы области является сжатие кода Сжатие кода сжимает имена переменных, имена функций и т. д. Однако на самом деле лишь немногие сценарии плагинов требуют сложного взаимодействия с областью видимости, поэтому областью применения этой части является Поговорим об этом здесь первый.
сделать плагин
Подожди, не уходи, это еще не конец, это только 2/3. Изучив вышеизложенное и получив знания, надо написать игрушечный плагин для пробы воды, верно?
Теперь план подражатьbabel-plugin-import, напишите минимальный плагин для реализации импорта модулей по запросу.В этом плагине мы будем импортировать операторы, подобные этому:
import {A, B, C as D} from 'foo'
преобразуется в:
import A from 'foo/A'
import 'foo/A/style.css'
import B from 'foo/B'
import 'foo/B/style.css'
import D from 'foo/C'
import 'foo/C/style.css'
сначала черезAST ExplorerВзгляните на структуру узла AST оператора импорта:
С результатами, показанными выше, нам нужно обработатьImportDeclaration
тип узла, установите егоspecifiers
Выньте его и пройдите через него. Кроме того, если пользователь использует默认导入
заявление, мы выдадим ошибку, чтобы напомнить пользователю, что импорт по умолчанию не может быть использован.
Базовая реализация выглядит следующим образом:
// 要识别的模块
const MODULE = 'foo'
traverse(ast, {
// 访问导入语句
ImportDeclaration(path) {
if (path.node.source.value !== MODULE) {
return
}
// 如果是空导入则直接删除掉
const specs = path.node.specifiers
if (specs.length === 0) {
path.remove()
return
}
// 判断是否包含了默认导入和命名空间导入
if (specs.some(i => t.isImportDefaultSpecifier(i) || t.isImportNamespaceSpecifier(i))) {
// 抛出错误,Babel会展示出错的代码帧
throw path.buildCodeFrameError("不能使用默认导入或命名空间导入")
}
// 转换命名导入
const imports = []
for (const spec of specs) {
const named = MODULE + '/' + spec.imported.name
const local = spec.local
imports.push(t.importDeclaration([t.importDefaultSpecifier(local)], t.stringLiteral(named)))
imports.push(t.importDeclaration([], t.stringLiteral(`${named}/style.css`)))
}
// 替换原有的导入语句
path.replaceWithMultiple(imports)
}
})
Логика довольно проста,babel-plugin-import
Это намного сложнее.
Далее мы оборачиваем его как стандартный плагин Babel. В соответствии со спецификацией нам необходимо создатьbabel-plugin-*
Префикс имени пакета:
mkdir babel-plugin-toy-import
cd babel-plugin-toy-import
yarn init -y
touch index.js
Вы также можете пройтиgenerator-babel-pluginдля создания шаблонов проектов.
существуетindex.js
Заполняем файл нашим кодом.index.js
По умолчанию функция экспортируется, и структура функции выглядит следующим образом:
// 接受一个 babel-core 对象
export default function(babel) {
const {types: t} = babel
return {
pre(state) {
// 前置操作,可选,可以用于准备一些资源
},
visitor: {
// 我们的访问者代码将放在这里
ImportDeclaration(path, state) {
// ...
}
},
post(state) {
// 后置操作,可选
}
}
}
Мы можем получить его из второго параметра метода доступаstate
Получить параметры, переданные пользователем. Предположим, что конфигурация пользователя:
{
plugins: [['toy-plugin', {name: 'foo'}]]
}
Мы можем получить параметры, переданные пользователем, следующим образом:
export default function(babel) {
const {types: t} = babel
return {
visitor: {
ImportDeclaration(path, state) {
const mod = state.opts && state.opts.name
if (mod == null) {
return
}
// ...
}
},
}
}
Готовая работа 🙏, релиз!
yarn publish # good luck
наконец
Дверь в новый мир открылась: ⛩
Эта статья в основном представляет архитектуру и принципы Babel, а также практикует разработку плагинов Babel.Прочитав это, вы вошли в дверь Babel.
Далее вы можете прочитатьВавилонское руководство, это лучший туториал на данный момент,ASTExplorerЭто лучшая тренировочная площадка, пишите больше кода и больше думайте. вы также можете посетитьОфициальная реализация плагина для Babel, на более высокий уровень.
В этой статье есть еще одна статья, которую я представлю в следующей статье.babel-plugin-macros, Следите за обновлениями!
Лайки для меня лучшая поддержка.