Сегодня мы рассмотрим процесс написания своего собственного плагина babel и опубликуем его в npm.
Babel - что это?
Исходя из официальной документации, babel - это набор инструментов, который позволяет преобразовывать код ECMAScript 2015+ в обратно совместимую версию JavaScript.
Например, плагин @babel/plugin-transform-react-jsx преобразовывает *.jsx в *.js:
jsx
const profile = (
<div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(" ")}</h3>
</div>
);
js
const profile = React.createElement(
"div",
null,
React.createElement("img", { src: "avatar.png", className: "profile" }),
React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
Вся эта магия происходит благодаря Abstract Syntax Tree (AST). Грубо говоря, AST представляет ваш код как дерево нод (объектов) с определенными свойствами.
Основная идея babel - это взять ваш код -> представить его в дереве AST -> произвести трансформацию узлов дерева -> сгенерировать код из обновленного AST.
Пишем свой плагин
Теперь когда мы познакомились с тем, как работает babel мы можем приступить к написанию собственного плагина. В этом примере плагин будет добавлять брать стили из конфига и применять их для выводов console.
Для начал создадим новый проект и добавим необходимые зависимости:
mkdir babel-plugin-styled-console-output
cd babel-plugin-styled-console-output
touch index.js
yarn add -D @babel/core
В index.js добавим следующий код:
const babel = require("@babel/core");
module.exports = function () {
const LogVisitor = {
CallExpression(path, state) {
const { callee, arguments } = path.node;
if (callee.object && callee.object.name === "console") {
const styles = state.opts.types[callee.property.name];
if (styles) {
let parsedStylesToString = "";
let divider;
Object.keys(styles).forEach((key) => {
if (key === "divider") {
divider = styles[key];
} else {
parsedStylesToString += `${key}: ${styles[key]}; `;
}
});
const str = arguments
.map((arg) => {
if (arg.type === "StringLiteral") {
return arg.value;
}
})
.join(divider || " ");
const newArgs = [
babel.types.stringLiteral(`%c${str}`),
babel.types.stringLiteral(parsedStylesToString),
];
path.node.arguments = newArgs;
path.node.callee.property.name = "log";
}
}
},
};
return { visitor: LogVisitor };
};
Теперь пройдем подробнее по тому что мы написали выше:
На строке 4 мы создаем новый объект, который возвращаем на строке 42. Тем самым мы создаем visitor, который будет проходить по нашему коду в момент запуска babel.
Внутри объекта (на строке 5) мы вызываем один из типов в ASTCallExpression
. Вы можете спросить откуда вообще взялся этот CallExpression? Хороший вопрос!
Есть замечательный инструмент Babel AST explorer. Давайте перейдем туда и напишем console.log('text');
Кликнув на строку 1, справа мы увидим вышеупомянутое дерево AST. Теперь мы видим что наш вызов console.log
представлен в дереве как узел CallExpression
с полями callee
и arguments
. Эти поля мы достаем на строке 6 нашего кода с помощью path.node
.
Теперь, когда мы немного понимаем что происходит под капотом, вернемся в наш код. На строке 8 мы проверяем что у нас действительно вызывается метод объекта Console
.
На строчке 9 мы достаем данные из нашего конфига (с помощью state.opts
), который выглядит следующим образом:
{
"types": {
"error": { "color": "white", "background": "blue", "divider": "---" },
}
}
Далее мы превращаем наш конфиг в строку, что бы в дальнейшем использовать ее как второй аргумент при вызове console.log
(встроенная возможность функции log
)
console.log('%csometext', 'color: red');
На строке 23 мы собираем все аргументы переданные в log
в одну строку и разделяем их с помощью divider
переданного в конфиг (или ' '
по умолчанию).
Затем на строке 31 мы создаем новый массив с аргументами и заменяем старые на строке 35. На 36 строке мы заменим .warn
, .error
и тд. на .log
(тк стили доступны только для log
функции).
На этом мы закончим с написание плагина и приступим к публикации его в npm.
Публикуем плагин в npm
Для публикации мы будем использовать yarn. В консоле вашего проекта напишите:
yarn init
и ответьте на вопросы.
Далее, следуя этой инструкции, мы должны войти/зарегестрироваться в npm.
После того, как вы вошли, запустите:yarn publish
и дождитесь окончания публикации.
Поздравляю! Мы опубликовали наш плагин в npm. Теперь настало время добавить его в проект и настроить.
Добавляем плагин в проект
Я взял свой первый попавшийся проект на React и добавил созданный плагин с помощью:
yarn add -D babel-formatted-log
Далее открыл файл .babelrc / babel.config.json
и добавил конфигурацию для плагина:
{
"plugins": [
// ...
[
"module:babel-formatted-log",
{
"types": {
"error": { "color": "white", "background": "blue", "divider": "---" },
"log": {
"border": "1px solid #d64021 ",
"font-weight": "800",
"padding": "10px"
}
}
}
]
]
}
Теперь соберем проект и напишем где-нибудь:
console.error("babel", "test");
console.log("text1", "text2", "text3");
В результате мы получим вывод похожий на этот:
Заключение
Сегодня мы написали свой собственный babel плагин и опубликовали его в npm. Этот плагин не несет какой-либо практической пользы, он создан в рамках ознакомления :)
Весь код и документация по плагину лежат на Github.
Спасибо что прочитали!