Babel — это транскодер, который в настоящее время используется при разработке проектов React и Vue. Он может преобразовывать синтаксис es6+ в es5, а также может преобразовывать синтаксис, такой как JSX и т. д. Фактически, он может выполнить любое преобразование с помощью пользовательских плагинов.
В наших проектах мы настраиваем плагины и пресеты (набор нескольких плагинов) для преобразования определенного кода, такого как env, stage-0 и т. д. Итак, как реализованы эти библиотеки?Давайте рассмотрим небольшой пример — поместите es6class
Преобразовать в es5.
Структура статьи:
- Конфигурация среды WebPack
- Как писать плагины для bable
- Как преобразовать класс es6 в es5
- Оптимизация наследования
- код после упаковки
конфигурация среды веб-пакета
Каждый должен был настроить загрузчик babel-core.На самом деле, его функция заключается только в предоставлении ядра Api Babel.Наше преобразование кода на самом деле достигается с помощью плагинов.
Далее мы сами реализуем плагин преобразования класса es6 без использования сторонних плагинов. Сначала выполните следующие шаги для инициализации проекта:
- npm install webpack webpack-cli babel-core -D
- Создать новый WebPack.config.js
- настроить webpack.config.js
Если имя нашего плагина хочет называться transform-class, нам нужно настроить следующее в конфигурации веб-пакета:
Далее мы создаем новую папку babel-plugin-transform-class в node_modules, чтобы написать логику плагина (если это реальный проект, вам нужно написать этот плагин и опубликовать его в репозиторий npm), как показано ниже:
Красная область — это моя только что созданная папка, это стандартная структура проекта плагина, для удобства моего плагина написан только основной файл index.js.
Как писать плагины для bable
Плагин babel на самом деле реализован через AST (абстрактное синтаксическое дерево).
babel помогает нам преобразовать js-код в AST, затем позволяет изменить его и, наконец, преобразовать в js-код.
Таким образом, возникает два вопроса: какова связь между кодом js и AST? Как заменить или добавить АСТ?
Ну, сначала представим инструмент:astexplorer.net:
Этот инструмент может преобразовать фрагмент кода в AST:
Как показано на рисунке, мы написали класс es6, а затем правая часть веб-страницы помогла нам сгенерировать AST, который фактически превратил каждую строку кода в объект, поэтому мы реализовали сопоставление.Еще один документ:babel-types:
Это документ API для создания узлов AST.
Например, мы хотим создать класс, сначала заходим на astexplorer.net, чтобы конвертировать, и обнаруживаем, что тип AST, соответствующий классу,ClassDeclaration
. Хорошо, давайте поищем в документации и обнаружим, что мы можем создать такой узел, вызвав следующий API:
Приступим собственно к написанию плагина, который разбит на следующие этапы:
- экспортировать функцию в index.js
- Функция возвращает объект, и объект имеет параметр посетителя (должен называться посетитель)
- Запрос через astexplorer.net
class
Соответствующий узел ASTClassDeclaration
- Установите функцию захвата в vistor
ClassDeclaration
, что означает, что я хочу захватить весь код jsClassDeclaration
узел - Напишите логический код для завершения преобразования
Вышеуказанные шаги соответствуют коду:
module.exports = function ({ types: t }) {
return {
visitor: {
ClassDeclaration(path) {
//在这里完成转换
}
}
};
}
В коде два параметра, первый{types:t}
Дело в том, чтобы деконструировать переменную t из параметров, которая на самом деле является t в документе типами babel (красный прямоугольник на рисунке ниже), мы используем этоt
Создайте узел:
Второй параметрpath
, что является информацией, соответствующей захваченному узлу, мы можем передатьpath.node
Получите AST этого узла и измените его на этой основе для достижения нашей цели.
Как преобразовать класс es6 в класс es5
Все это подготовительная работа, теперь начинается настоящая логика, сначала рассмотрим два вопроса:
- Мы должны сделать следующее преобразование, сначала превратить класс ES6 в класс ES5 (то есть обычную функцию), мы заметили, что многие коды могут быть мультиплексированы, включая имя функции, блок кода внутри функции.
- Если вы не определите класс в
constructor
метод, движок JavaScript автоматически добавляет пустойconstructor()
метод, который требует от нас обработки совместимости.
Далее мы начинаем писать код, идея такова:
- Получить старый узел AST
- Создайте массив для удержания нового узла AST (хотя исходный класс - это только один узел, он будет заменен несколькими функциональными узлами после замены)
- инициализировать значение по умолчанию
constructor
Узел (как упоминалось выше, в классе может не быть конструктора) - Зациклить объект AST старого узла (несколько функциональных узлов будут зациклены)
- Определите, не является ли тип узла
constructor
, если это так, создайте нормальный функциональный узел из старых данных и обновите значение по умолчаниюconstructor
узел - с остальным не справиться
constructor
узел, созданный из старых данныхprototype
тип функции и положитьes5Fns
середина - конец цикла, положить
constructor
узел также находится вes5Fns
середина - Определите, больше ли длина es5Fns 1, если она больше 1, используйте
replaceWithMultiple
Это API обновляет AST
module.exports = function ({ types: t }) {
return {
visitor: {
ClassDeclaration(path) {
//拿到老的AST节点
let node = path.node
let className = node.id.name
let classInner = node.body.body
//创建一个数组用来成盛放新生成AST
let es5Fns = []
//初始化默认的constructor节点
let newConstructorId = t.identifier(className)
let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier('')], t.blockStatement([]), false, false)
//循环老节点的AST对象
for (let i = 0; i < classInner.length; i++) {
let item = classInner[i]
//判断函数的类型是不是constructor
if (item.kind == 'constructor') {
let constructorParams = item.params.length ? item.params[0].name : []
let newConstructorParams = t.identifier(constructorParams)
let constructorBody = classInner[i].body
constructorFn = t.functionDeclaration(newConstructorId, [newConstructorParams], constructorBody, false, false)
}
//处理其余不是constructor的节点
else {
let protoTypeObj = t.memberExpression(t.identifier(className), t.identifier('prototype'), false)
let left = t.memberExpression(protoTypeObj, t.identifier(item.key.name), false)
//定义等号右边
let prototypeParams = classInner[i].params.length ? classInner[i].params[i].name : []
let newPrototypeParams = t.identifier(prototypeParams)
let prototypeBody = classInner[i].body
let right = t.functionExpression(null, [newPrototypeParams], prototypeBody, false, false)
let protoTypeExpression = t.assignmentExpression("=", left, right)
es5Fns.push(protoTypeExpression)
}
}
//循环结束,把constructor节点也放到es5Fns中
es5Fns.push(constructorFn)
//判断es5Fns的长度是否大于1
if (es5Fns.length > 1) {
path.replaceWithMultiple(es5Fns)
} else {
path.replaceWith(constructorFn)
}
}
}
};
}
Оптимизация наследования
На самом деле классы тоже предполагают наследование, и идея не сложная, просто определить есть ли в AST таковоеsuperClass
свойства, если они есть, нам нужно добавить дополнительную строку кодаBird.prototype = Object.create(Parent)
, конечно, не забудьте обработатьsuper
ключевые слова.
код после упаковки
бегатьnpm start
После упаковки видим файлы в пакетеclass
Грамматика была успешно преобразована в функции es5 одна за другой.
конец
Итак, конвертер классов написан, надеюсь, он поможет вам немного разобраться в Babel.