Введение
Babel — это инструмент Node.js для использования JavaScript следующего поколения. В этой статье объясняется, как Babel предназначен для решения этой проблемы, на основе исходных кодов основной ветки за ноябрь 2019 года.
Как устроен Вавилон?
babel-loader принадлежит проекту webpack, которого нет в репозитории Babel.
Слой фреймворка
Общие компиляторы
Есть acorn, @babel/parser (babylon), flow, traceur, typescript, uglify-js и т.д. AST этих компиляторов почти одинаковы.
Как устроен @babel/parser
Важные концепции
- Литерал: включая логические, числовые и строковые значения.
- Идентификатор: включая имена переменных, undefined и null и т.д.
- Вал: Значение. Есть два вида значений: левые значения и правильные значения. Значения слева представляют узлы, которые могут быть назначены, например:
[a]
. В основном это шаблоны или идентификаторы. Правые значения представляют узлы, которые передают определенные значения, напримерb.c
. В основном это выражения, идентификаторы и литералы. Значения слева и справа связаны знаками равенства для представления выражений присваивания, например[a] = b.c
. - Выражение: часто используется как правильное значение. Существуют такие выражения, как MemberExpression, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression и т.д.
- Заявление: часто состоит из выражений. ExpressionStatements часто встречаются.
- Программа: все коды, содержащиеся в программе. Программа содержит несколько параллельных операторов.
let c = 0;
while (a < 10) {
const b = a % 2;
if (b == 0) {
c++;
}
}
console.log(c);
Приведенные выше коды преобразуются в приведенный ниже AST с помощью @babel/parser:
9 уровней наследования @babel/parser
- Парсер: Инициализация
- StatementParser: анализирует операторы и объединяет их в программу. 2100+ строк кода.
- ExpressionParser: более 2400 строк кода.
- LValParser: анализирует левые значения, преобразует узел в назначаемый узел. например преобразовать ArrayExpression в ArrayPattern.
- NodeUtils: обеспечивает операции AST. например копировать узел.
- UtilParser: Предоставляет утилиты, например. чтобы сказать, является ли это концом строки.
- Токенизатор: 1400+ строк кодов.
- LocationParser: записывает расположение строк и столбцов.
- CommentsParser: анализирует комментарии.
- BaseParser: Предоставляет возможности плагина.
Большинство модулей составляют около 100 строк. За исключением StatementParser, ExpressionParser и Tokenizer, они имеют сложную логику.
@бабель/траверс
@babel/traverse предоставляет способ обойти все узлы AST, например:
traverse(ast, { FunctionDeclaration: function(path) { path.node.id.name = "x"; } });
traverse(ast, { enter(path) { if (path.isIdentifier({ name: "n" })) { path.node.name = "x"; } } });
path
— это объект, представляющий связь между двумя узлами, с некоторыми свойствами и методами, перечисленными ниже:
Характеристики:
- узел: дочерний узел
- родитель: родительский узел
- parentPath: родительский путь
- область: текущая область
Методы:
- get: получить свойства AST дочернего узла
- findParent: найти родительские пути
- getSibling: получить одноуровневые пути
- getFunctionParent: получить ближайший родительский путь, который является типом функции
- getStatementParent: получить ближайший родительский путь, который является типом оператора
- replaceWith: заменить дочерний узел вводом
- replaceWithMultiple: заменить дочерний узел несколькими входами
- replaceWithSourceString: заменить дочерний узел строкой исходного кода, Babel сначала преобразует исходную строку в AST
- insertBefore: вставить ввод перед дочерним узлом в качестве его silbing перед
- insertAfter: вставить ввод после дочернего узла в качестве его silbing после
- удалить: удалить дочерний узел
- pushContainer: поместить ввод в массив, подобный свойству узла
@babel/генератор
@babel/generator преобразует AST в исходную строку. например.:
import { parse } from '@babel/parser'; import generate from '@babel/generator';
const ast = parse('class Example {}'); generate(ast); // => { code: 'class Example {}' }
Он может генерировать исходную карту. например.:
import { parse } from '@babel/parser'; import generate from '@babel/generator';
const code = 'class Example {}'; const ast = parse(code);
const output = generate(ast, { sourceMaps: true, sourceFileName: code }); // => { code: 'class Example {}', rawMappings: ... } // or const output = generate(ast, { sourceMaps: true, sourceFileName: 'source.js' }, code); // => { code: 'class Example {}', rawMappings: ... }
Он может объединять несколько исходных файлов вместе и генерировать исходную карту. например.:
import { parse } from '@babel/parser'; import generate from '@babel/generator';
const a = 'var a = 1;'; const b = 'var b = 2;'; const astA = parse(a, { sourceFilename: 'a.js' }); const astB = parse(b, { sourceFilename: 'b.js' }); const ast = { type: 'Program', body: [...astA.program.body, ...astB.program.body] };
const { code, map } = generate(ast, { sourceMaps: true }, { 'a.js': a, 'b.js': b });
@бабель/ядро
@babel/core предоставляет API transform
и parse
.
transform
API состоит из шагов синтаксического анализа -> перемещения -> создания.
API parse
в основном представляет собой оболочку @babel/parser.
Уровень реализации
@babel/плагин
@babel/плагин-синтаксис-x
Они обеспечивают возможность разбора синтаксиса путем включения переключателей синтаксиса. В @babel/parser, если переключатель синтаксиса включен, он анализирует определенный синтаксис. В противном случае возникает синтаксическая ошибка. Давайте посмотрим, например, на @babel/plugin-syntax-jsx:
parserOpts.plugins.push("jsx");
@babel/plugin-transform-x
Они обеспечивают преобразование JavaScript последнего поколения в JavaScript прошлого поколения. Как @babel/plugin-transform-exponentiation-operator:
export default {
name: "transform-exponentiation-operator",
visitor: build({
operator: "**",
build(left, right) {
return t.callExpression(
t.memberExpression(t.identifier("Math"), t.identifier("pow")),
[left, right],
);
},
}),
}
@babel/plugin-proposal-x
Они обеспечивают преобразование JavaScript следующего поколения в JavaScript прошлого поколения. Например: @babel/plugin-proposal-numeric-separator:
export default { name: "proposal-numeric-separator", inherits: syntaxNumericSeparator,
visitor: { CallExpression: replaceNumberArg, NewExpression: replaceNumberArg, NumericLiteral({ node }) { const { extra } = node; if (extra && /_/.test(extra.raw)) { extra.raw = extra.raw.replace(/_/g, ""); } }, }, }
@babel/предустановка-x
Они предоставляют наборы комбинированных плагинов, синтаксиса и помощников.
Самый распространенный пресет — это @babel/preset-env, который использует список браузеров, чтобы решить, какой уровень преобразования прошлого поколения следует использовать.
@бабель/полифилл
Это устарело, начиная с Babel 7.4.0. Сейчас на замену рекомендуется core-js и regenerator-runtime. core-js предоставляет полифиллы ECMAScript, а regenerator-runtime предоставляет среды выполнения для асинхронных функций и функций генератора.
@babel/помощники
Он определяет вспомогательные функции для среды выполнения Babel. например classCallCheck используется для проверки того, что функция вызывается как класс, и она вставляется в определение класса.
В плагине хелперы называются так:
export default {
visitor: {
ClassExpression(path) {
this.addHelper("classCallCheck");
// ...
}
};
Сгенерированный код будет содержать classCallCheck:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Circle = function Circle() { _classCallCheck(this, Circle); };
@babel/время выполнения
Он предоставляет среды выполнения Babel, включая среду выполнения регенератора. Среды выполнения предоставляют некоторые вспомогательные коды. например.:
Когда мы используем @babel/helpers:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Circle = function Circle() { _classCallCheck(this, Circle); };
Пока мы используем @babel/plugin-transform-runtime для сокращения таких кодов:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() { _classCallCheck(this, Circle); };
В репозитории исходного кода @babel/runtime нет кода. Сценарий сборки копирует @babel/helpers в @babel/runtime.
Помимо этой среды выполнения, Babel предоставляет @babel/runtime-corejs2 и @babel/runtime-corejs3. Они основаны на core-js v2 и v3. Вы можете использовать их, установив опцию corejs в конфигах @babel/plugin-transform-runtime.
Слой помощников
@бабель/типы
Это предоставляет базовые типы узлов AST и фабрики узлов AST, чтобы упростить управление узлами AST @babel/plugin и @babel/parser.
const binaryExpression = t.binaryExpression('+', t.numericLiteral(1), t.numericLiteral(2))
@babel/кодовый кадр
Это распечатывает кадр кода, чтобы объяснить ошибку пользователям. например.:
import { codeFrameColumns } from '@babel/code-frame';
const rawLines = `class Foo { constructor() }`; const location = { start: { line: 2, column: 16 } }; codeFrameColumns(rawLines, location);
Результат:
1 | class Foo {
> 2 | constructor()
| ^
3 | }
@babel/изюминка
Это обеспечивает подсветку синтаксиса в терминале.
import highlight from "@babel/highlight";
const code = `class Foo { constructor() }`; highlight(code); // => "\u001b[36mclass\u001b[39m \u001b[33mFoo\u001b[39m {\n constructor()\n}"
В терминале выводит:
@бабель/шаблон
Шаблонизатор Babel.
import template from "@babel/template"; import generate from "@babel/generator"; import * as t from "@babel/types";
const buildRequire = template(` var %%importName%% = require(%%source%%); `);
const ast = buildRequire({ importName: t.identifier("myModule"), source: t.stringLiteral("my-module"), });
generate(ast).code // => var myModule = require('my-module');
@babel/помощник-х
Он содержит вспомогательные функции Babel, в том числе общие утилиты, вспомогательные функции для тестирования и так далее.
Прикладной уровень
@бабель/кли
Это преобразует коды в терминале.
babel script.js # prints out the transformed codes
@babel/автономный
Это преобразует коды в браузере. например сайт Babel использует этот модуль.
<div id="input"></div>
<div id="output"></div>
<button id="transform">Transform</button>
<!-- Load @babel/standalone -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script>
document.getElementById('transform').addEventListener('click', function() {
const input = document.getElementById('input').value;
const output = Babel.transform(input, { presets: ['es2015'] }).code;
document.getElementById('output').value = output;
});
</script>
@babel/standalone также может автоматически преобразовывать и выполнять коды в тегах <script type="text/babel"></script>
и <script type="text/jsx"></script>
.
<div id="output"></div>
<!-- Load @babel/standalone -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- ES2015 will be transformed and executed -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
@babel/узел
Это позволяет вам выполнять JavaScript следующего поколения в командной строке. @babel/cli — это интерфейс командной строки для преобразования кодов, но не для их выполнения. В то время как @babel/node преобразует и выполняет коды. Этот модуль не подходит для производства.
babel-node -e script.js # You can use next-generation JavaScript in script.js
@babel/зарегистрироваться
Это позволяет вам требовать файл JavaScript следующего поколения в среде Node.js. Это очень похоже на @babel/node. Это тоже не подходит для производства.
require("@babel/register")();
require("./script.js"); // You can use next-generation JavaScript in script.js
Пример выходных данных преобразования синтаксиса
Array.from
// input Array.from([1, 2, 3])
// output var _array_from_ = require('@babel/runtime-corejs3/core-js-stable/array/from'); _array_from_([1, 2, 3]);
JSX
// input <div className="text">{content}</div>
// output React.createElement('div', { className: 'text' }, content);
class
// input class Example extends Component { constructor(props) { super(props) } }
// output var _inherits_ = require('@babel/runtime-corejs3/helpers/interits'); var _class_call_check_ = require('@babel/runtime-corejs3/helpers/classCallCheck'); var _possible_constructor_return_ = require('@babel/runtime-corejs3/helpers/possibleConstructorReturn'); var _get_prototype_of_ = require('@babel/runtime-corejs3/helpers/getPrototypeOf'); var _create_class_ = require('@babel/runtime-corejs3/helpers/createClass');
var Example = function (_Component) { _inherits_(Example, _Component);
function Example(props) { _class_call_check_(this, Example);
return _possible_constructor_return_(this, _get_prototype_of_(Example).call(this, props)); }
_create_class_(Example, []);
return Example; }(Component);