В этом руководстве мы выполним некоторые базовые преобразования исходного кода с помощью Babel. Многие люди находят идею преобразования кода пугающей и недоступной, но, используя мощь AST (абстрактных синтаксических деревьев) и набор инструментов, предоставленных нам Babel, большая часть тяжелой работы делается за нас.
Примечание. Примеры в статье будут включать код, специфичный для react
, redux
и react-redux
, но знакомство с этими библиотеками не обязательно для этого руководства.
AST Explorer
Существует веб-сайт под названием AST explorer, на который мы можем вставить наш код и получить представление AST во многих форматах. Этот веб-сайт будет полезен для быстрого просмотра кода в формате AST и будет полезен при определении того, на какие узлы нам нужно настроить таргетинг.
Базовая установка
Ниже у нас есть файл reducers.js
с парой операций импорта и экспортом по умолчанию.
Для нашего первого преобразования давайте добавим новый импорт и экспорт в reducers.js
. Мы добавим mice
. Для этого нам необходимо:
- Разберите код в формат AST.
- Пройдите по AST и найдите узлы, смежные с узлами, которые мы хотим добавить.
- Вставьте новые узлы.
- Сгенерируйте новый код из нашего 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);
Это состоит из двух шагов.
- Получите имя экспорта по умолчанию (идентификатор).
- Замените его собственной версией в оболочке и функцией
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!