Введение

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);

Ссылка