В этом руководстве мы выполним некоторые базовые преобразования исходного кода с помощью Babel. Многие люди находят идею преобразования кода пугающей и недоступной, но, используя мощь AST (абстрактных синтаксических деревьев) и набор инструментов, предоставленных нам Babel, большая часть тяжелой работы делается за нас.

Примечание. Примеры в статье будут включать код, специфичный для react, redux и react-redux, но знакомство с этими библиотеками не обязательно для этого руководства.

AST Explorer

Существует веб-сайт под названием AST explorer, на который мы можем вставить наш код и получить представление AST во многих форматах. Этот веб-сайт будет полезен для быстрого просмотра кода в формате AST и будет полезен при определении того, на какие узлы нам нужно настроить таргетинг.

Базовая установка

Ниже у нас есть файл reducers.js с парой операций импорта и экспортом по умолчанию.

Для нашего первого преобразования давайте добавим новый импорт и экспорт в reducers.js. Мы добавим mice. Для этого нам необходимо:

  1. Разберите код в формат AST.
  2. Пройдите по AST и найдите узлы, смежные с узлами, которые мы хотим добавить.
  3. Вставьте новые узлы.
  4. Сгенерируйте новый код из нашего AST.

Вот как мы этого достигаем:

Давайте разберемся с этим. Прежде всего, мы вызываем синтаксический анализатор нашего кода, который преобразует его из строки в AST.

const ast = parser(file, {sourceType: 'module'});

Обратите внимание, поскольку мы используем модули ES6, нам нужно сообщить парсеру об этом с помощью {sourceType: ‘module’}.

Затем мы используем traverse, чтобы найти соответствующие узлы. Как мы узнали, что нам нужны ExportDefaultDeclaration и ObjectExpression? Вот тут-то и пригодится AST explorer. Ниже мы вставили наш код на левую панель, а справа мы можем просмотреть AST-представление нашего кода.

У нас есть 2 ImportDeclaration, поэтому traverse поможет нам перебрать их и сохранить последний в переменной с именем lastImport. Затем мы используем insertAfter, чтобы вставить новый импорт после последнего импорта.

// this file is made up of snippets from transform.js
let lastImport;
traverse(ast, {
  ImportDeclaration(path) {
  lastImport = path;
}
const importCode = `import ${reducerName} from './${reducerName}'`;
lastImport.insertAfter(parser(importCode, {sourceType: 'module'}));

Чтобы добавить свойство к экспортируемому объекту по умолчанию, мы будем использовать traverse для перебора ObjectExpression. Мы ожидаем, что он будет только один, поэтому сохраним его свойства с помощью properties = path.parent.declaration.properties. Затем мы можем вставить наш новый идентификатор mice в массив properties.

// this file is made up of snippets from transform.js
traverse(ast, {
  ObjectExpression(path) {
    properties = path.parent.declaration.properties
  }
})
const id = t.identifier(REDUCER_NAME)
properties.push(t.objectProperty(id, id, false, true))

Вам может быть интересно, что такое t.objectProperty(id, id, false, true)? Хороший вопрос. Поскольку одному mice не хватает контекста, мы не можем просто вызвать parser для строки кода, как в последнем примере. Babel анализировал бы его как Identifier вместо Property, что приводило к проблемам при повторной генерации кода. Чтобы решить эту проблему, мы используем пакет @babel/types, чтобы помочь синтаксическому анализатору понять, что мы добавили в AST.

Теперь, когда мы обновили наш AST, мы можем вызвать generate. Это преобразует код из AST обратно в код в строковом формате. Мы запускаем более красивую строку, и в итоге получаем код, как показано ниже:

Обертывание переменной в функции

Далее мы узнаем, как использовать replaceWith для обертывания идентификатора в компоненте высокого порядка.

По сути, мы хотим перейти от:

export default Sports;

to:

const mapStateToProps = ({ volleyball, soccer }) => ({
  volleyball,
  soccer
});
export default connect(mapStateToProps)(Sports);

Это состоит из двух шагов.

  1. Получите имя экспорта по умолчанию (идентификатор).
  2. Замените его собственной версией в оболочке и функцией mapStateToProps.

Вот файл, с которым мы будем работать:

А вот код для его преобразования:

Первое, что следует отметить, поскольку на этот раз мы анализируем JSX, нам нужно сообщить парсеру:

const ast = parser(file, {sourceType: 'module', plugins: ['jsx']});

На этот раз мы используем traverse, чтобы перебрать AST и найти ExportDefaultDeclaration. Как только мы его нашли, мы сохраняем имя экспортируемой переменной.

const declarationName = exportDefaultPath.node.declaration.name;

Поскольку мы знаем имя экспортируемой переменной, теперь мы можем заменить весь экспорт по умолчанию новым кодом:

exportDefaultPath.replaceWith(
  // new code...
)

Преобразовав AST, мы можем запустить на нем generate и prettier и записать файл на диск. В итоге получаем:

Подводя итоги

Изучение того, как управлять AST, откроет вам много новых возможностей. С помощью AST вы можете написать:

  • Плагины Linting
  • Плагины Babel
  • Codemods

Я надеюсь, что это руководство поможет вам познакомиться с миром AST!