Начало работы с babel — реализация конвертера классов es6

внешний интерфейс JavaScript Webpack Babel
Начало работы с babel — реализация конвертера классов es6

Babel — это транскодер, который в настоящее время используется при разработке проектов React и Vue. Он может преобразовывать синтаксис es6+ в es5, а также может преобразовывать синтаксис, такой как JSX и т. д. Фактически, он может выполнить любое преобразование с помощью пользовательских плагинов.
В наших проектах мы настраиваем плагины и пресеты (набор нескольких плагинов) для преобразования определенного кода, такого как env, stage-0 и т. д. Итак, как реализованы эти библиотеки?Давайте рассмотрим небольшой пример — поместите es6classПреобразовать в 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.netclassСоответствующий узел ASTClassDeclaration
  • Установите функцию захвата в vistorClassDeclaration, что означает, что я хочу захватить весь код jsClassDeclarationузел
  • Напишите логический код для завершения преобразования

Вышеуказанные шаги соответствуют коду:

module.exports = function ({ types: t }) {
    return {
        visitor: {
            ClassDeclaration(path) {
                //在这里完成转换
            }
        }
    };
}

В коде два параметра, первый{types:t}Дело в том, чтобы деконструировать переменную t из параметров, которая на самом деле является t в документе типами babel (красный прямоугольник на рисунке ниже), мы используем этоtСоздайте узел:

Второй параметрpath, что является информацией, соответствующей захваченному узлу, мы можем передатьpath.nodeПолучите AST этого узла и измените его на этой основе для достижения нашей цели.

Как преобразовать класс es6 в класс es5

Все это подготовительная работа, теперь начинается настоящая логика, сначала рассмотрим два вопроса:
  1. Мы должны сделать следующее преобразование, сначала превратить класс ES6 в класс ES5 (то есть обычную функцию), мы заметили, что многие коды могут быть мультиплексированы, включая имя функции, блок кода внутри функции.

  1. Если вы не определите класс в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.

Справочное содержание

руководство по разработке плагина github-babel
babel-types