Познакомьтесь с Babel на одном (долгом) дыхании

внешний интерфейс Babel

В последние годы, если вы являетесь фронтенд-разработчиком, если вы не использовали или даже не слышали о Babel, вас можно считать путешественником, верно?

Когда дело доходит до babel, всплывает ряд существительных:

  • babel-cli
  • babel-core
  • babel-runtime
  • babel-node
  • babel-polyfill
  • ...

Это все бейблы? Что они делают? Есть ли разница?

что именно сделал Бабель? как?

Проще говоря, новый синтаксис es2015/2016/2017/2046 в JavaScript преобразуется в es5, чтобы недорогие среды выполнения (такие как браузеры и узлы) могли распознавать и выполнять. Эта статья основана на Babel 6.x для обсуждения. Недавно компания babel выпустила версию 7.x, о которой я расскажу в конце.

Строго говоря, babel также можно преобразовать в более низкую спецификацию. Но в нынешнем виде спецификации es5 достаточно для охвата большинства браузеров, поэтому переход на es5, как правило, безопасен и популярен.

Если вы мало что знаете о es5/es2015 и т. д., вам может понадобиться сначала составить класс.

инструкции

Всего есть три способа:

  1. Использовать один файл (автономный скрипт)
  2. командная строка (cli)
  3. Плагины для инструментов сборки (babel-loader для webpack, rollup-plugin-babel для rollup).

Последние два встречаются чаще. Второй чаще встречается в package.json.scriptsКоманда в абзаце, третья напрямую интегрирована в инструмент сборки.

Единственная разница между этими тремя методами — это вход.Вызываемое ядро ​​Babel обрабатывается одинаково, так что не будем беспокоиться о входе.

Время выполнения и плагины

Babel делится на три этапа: анализ, преобразование и генерация.

Сам Babel не имеет никакой функции преобразования, он разбивает функции преобразования на плагины один за другим. Поэтому, когда мы не настраиваем никаких плагинов, код и входные данные, проходящие через Babel, остаются теми же.

Существует два типа плагинов:

  1. когда мы добавляемплагин синтаксисаПосле этого этап синтаксического анализа позволяет babel анализировать больше грамматик. (Кстати, библиотека синтаксического анализа, используемая для внутреннего использования Babel, называется babylon и не разработана Babel.)

Для простого примера, когда мы определяем или вызываем метод, запятые не допускаются после последнего параметра, напримерcallFoo(param1, param2,)является незаконным. Если исходный код написан таким образом, после babel будет выдана синтаксическая ошибка.

Но недавние предложения JS позволили этот новый способ написания (чтобы сделать различия в коде более понятными). Чтобы избежать ошибок в сообщениях Babel, вам необходимо добавить синтаксические плагины.babel-plugin-syntax-trailing-function-commas

  1. когда мы добавляемПлагин переводаПосле этого на этапе преобразования исходный код преобразуется и выводится. Это также самое важное требование для использования Babel.

По сравнению с плагинами синтаксиса плагины перевода на самом деле лучше понятны, например, функции стрелок.(a) => aбудет преобразован вfunction (a) {return a}. Плагин, который выполняет эту работу, называетсяbabel-plugin-transform-es2015-arrow-functions.

Версия плагина грамматики и версия плагина перевода могут существовать для одного и того же вида грамматики.Если мы используем плагин транспилера, нам больше не нужно использовать плагин синтаксиса.

конфигурационный файл

Поскольку плагины являются основой Babel, как их использовать? Всего 2 шага:

  1. Добавьте название плагина в конфигурационный файл (создайте .babelrc или package.json в корневом каталогеbabelвнутри тот же формат)
  2. использоватьnpm install babel-plugin-xxxустановить

Конкретный формат записи подробно не описывается.

preset

Например, es2015 — это набор спецификаций, включающий около дюжины или 20 плагинов для перевода. Если вы хотите, чтобы разработчики добавляли и устанавливали по одному каждый раз, файл конфигурации будет очень длинным.npm installЭто также будет долго, не говоря уже о том, что нам, возможно, придется использовать другие спецификации одновременно.

Для решения этой проблемы Babel также предоставляет набор плагинов. Поскольку он широко используется, нет необходимости повторять определение и установку. (Разница между одной точкой и пакетом в том, что пакет экономит много времени и усилий по настройке)

Пресеты делятся на следующие категории:

  1. Официальный контент, в настоящее время включающий env, react, flow, minify и т. д. Самое главное здесь — это env, о котором будет подробно рассказано позже.

  2. stage-x, который содержит проект последней спецификации года, обновляется каждый год.

    Он также подразделяется на

    • Этап 0 — Пугало: Просто идея, которую придумали участники TC39.
    • Этап 1 - Предложение: первоначальная попытка.
    • Этап 2 - Первый проект: Заполните предварительную спецификацию.
    • Этап 3 — Кандидат: Полная спецификация и предварительная реализация браузера.
    • Этап 4 — Завершение: будет добавлено в следующий ежегодный выпуск.

    Напримерsyntax-dynamic-importявляется содержанием этапа-2,transform-object-rest-spreadЭто содержание стадии-3.

    Кроме того, нижний этап будет содержать все содержимое более высокого уровня, например этап-1 будет содержать все содержимое этапа-2, этапа-3.

    Обновление Stage-4 будет помещено непосредственно в env в следующем году, поэтому нет необходимости использовать отдельный Stage-4.

  3. es201x, latest

    Это грамматики, включенные в стандартную спецификацию. Например, es2015 содержитarrow-functions, es2017 содержитsyntax-trailing-function-commas. Но из-за появления env и es2016, и es2017 были заброшены. Таким образом, мы часто видим es2015 в списке отдельно, но редко два других.

    Последний является прототипом env, это предустановка, обновляемая каждый год, цель состоит в том, чтобы включить все es201x. Но также из-за появления более гибких env от него отказались.

исполнительный лист

Несколько простых принципов:

  • Плагин запустится перед пресетом.
  • Плагины выполняются последовательно спереди назад.
  • Порядок предустановкикак раз наоборот(сзади на перед).

Обратный порядок пресетов в первую очередь для обратной совместимости, так как большинство пользователей пишут в порядке['es2015', 'stage-0']. Это должно быть выполнено в первую очередьstage-0Чтобы убедиться, что babel не сообщает об ошибке. Поэтому, когда мы расставляем пресеты, мы также должны обращать внимание на порядок.На самом деле, пока они перечислены в хронологическом порядке.

Элементы конфигурации для плагинов и пресетов

Короче говоря, плагины и пресеты просто должны перечислить свои имена в строковом формате. Но если предустановке или плагину нужны какие-то элементы конфигурации (или параметры), вам нужно сначала превратить себя в массив. Первый элемент по-прежнему является строкой, представляющей собственное имя, а второй элемент является объектом, объектом конфигурации.

Наиболее важная вещь для настройки — это env, а именно:

"presets": [
    // 带了配置项,自己变成数组
    [
        // 第一个元素依然是名字
        "env",
        // 第二个元素是对象,列出配置项
        {
          "module": false
        }
    ],

    // 不带配置项,直接列出名字
    "stage-2"
]

окружение (выделение)

Поскольку env является наиболее часто используемым и наиболее важным, нам нужно сосредоточиться на нем.

Основная цель env — узнать характеристики целевой среды посредством конфигурации, а затем выполнить только необходимые преобразования. Например, если целевой браузер поддерживает es2015, то пресет es2015 фактически не нужен, поэтому код может быть меньше (как правило, преобразованный код всегда длиннее), а время сборки может быть сокращено.

Если вы не прописываете какие-либо элементы конфигурации, env эквивалентен last, что также эквивалентно добавлению es2015 + es2016 + es2017 (исключая плагины в stage-x). Список плагинов, включенных в env, хранится по адресуздесь

Вот некоторые из наиболее часто используемых методов настройки:

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }]
  ]
}

Приведенная выше конфигурация будет учитывать характеристики последних 2-х версий всех браузеров (версия сафари выше или равна 7.0) и конвертировать необходимый код. Существующие функции этих версий не будут преобразованы. Синтаксис здесь может относиться кbrowserslist

{
  "presets": [
    ["env", {
      "targets": {
        "node": "6.10"
      }
    }]
  ]
}

Приведенная выше конфигурация устанавливает цель для nodejs и поддерживает версии 6.10 и выше. также можно использоватьnode: 'current'для поддержки последней стабильной версии. Например, стрелочные функции не будут преобразованы в nodejs 6 и выше, но будут преобразованы в nodejs 0.12.

Еще один полезный элемент конфигурацииmodules. Его значение может бытьamd, umd, systemjs, commonjsиfalse. Это позволяет babel выводить код в определенном модульном формате. Если вы выберетеfalseМодульная обработка не выполняется.

Другие вспомогательные инструменты

Основной механизм обработки и методы конфигурации babel обсуждались выше, и это способ вызова babel независимо от какой-либо записи. А вот упомянутая в начале статьи кучаbabel-*Все еще сбивает с толку. На самом деле этиbabel-*Большинство из них представляют собой разные входы (методы) использования babel, давайте кратко представим их ниже.

babel-cli

Как следует из названия, cli — это инструмент командной строки. установленbabel-cliможно использовать в командной строкеbabelкоманда для компиляции файла.

Следующие шаблоны часто используются при разработке пакетов npm:

  • Пучокbabel-cliустановить какdevDependencies
  • добавить в package.jsonscripts(Напримерprepublish),использоватьbabelкоманда для компиляции файла
  • npm publish

Это позволяет писать исходный код с использованием более нового канонического синтаксиса JS, сохраняя при этом поддержку устаревших сред. Поскольку проект может быть не слишком большим и не нуждаться в инструменте сборки (веб-пакет или накопительный пакет), перед выпуском используйте его.babel-cliдля обработки.

babel-node

babel-nodeдаbabel-cliчасть, его не нужно устанавливать отдельно.

Его роль заключается в запуске кода es2015 непосредственно в среде узла без дополнительного перекодирования. Например, у нас есть файл js, написанный с синтаксисом es2015 (например, с использованием стрелочных функций). мы можем напрямую использоватьbabel-node es2015.jsВыполнить вместо транскодирования.

Можно сказать:babel-node = babel-polyfill + babel-register. Кто эти двое?

babel-register

переписать модуль babel-registerrequireкоманду, добавьте к ней хук. После этого при каждом использованииrequireнагрузка.js,.jsx,.esи.es6Файл с суффиксным именем будет сначала перекодирован с помощью babel.

При использовании он должен быть загружен первымrequire('babel-register').

Следует отметить, что babel-register будет толькоrequireзагружаемые командой файлы перекодируются, ане будет перекодировать текущий файл.

Кроме того, поскольку он перекодирует в реальном времени,Подходит только для использования в среде разработки.

babel-polyfill

Babel по умолчанию преобразует только синтаксис js, а не новые API, такие как глобальные объекты, такие как Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise, и некоторые методы, определенные для глобальных объектов (например,Object.assign) не будет перекодирован.

Например, es2015 добавлен к объектам массива.Array.fromметод. Babel не будет перекодировать этот метод. Если вы хотите, чтобы этот метод работал, вы должны использоватьbabel-polyfill. (интегрировано внутриcore-jsиregenerator)

При использовании увеличивать перед запуском всего кодаrequire('babel-polyfill'). или более обычная операция находится вwebpack.config.jsгенерал-лейтенантbabel-polyfillкак первая запись. Поэтому должно бытьbabel-polyfillв видеdependenciesвместоdevDependencies

babel-polyfillЕсть два основных недостатка:

  1. использоватьbabel-polyfillЭто приведет к тому, что пакет будет очень большим, потому чтоbabel-polyfillпредставляет собой единое целое, добавляя все методы в цепочку прототипов. Например, мы использовали толькоArray.from, но ставитObject.definePropertyЕго также добавляют, что является расточительством. Эту проблему можно решить, используяcore-jsОпределенная библиотека классов для решения,core-jsвсе отдельно.

  2. babel-polyfillЭто загрязнит глобальные переменные и изменит цепочку прототипов многих классов.Если мы разработаем библиотеку классов для использования другими разработчиками, эта ситуация станет очень неконтролируемой.

Таким образом, в реальном использовании, если мы не можем вынести эти два недостатка (особенно второй), мы обычно склонны использоватьbabel-plugin-transform-runtime.

Но если код содержит методы экземпляров типов в более высокой версии js (например,[1,2,3].includes(1)), для которого по-прежнему требуется полифилл.

babel-runtime и babel-plugin-transform-runtime (выделено)

Мы часто видим, что .babelrc используется в проектах.babel-plugin-transform-runtimepackage.jsonсерединаdependencies(Обратите внимание, что это неdevDependencies) также включаетbabel-runtime, эти два используются в наборах? Что они делают?

во-первыхbabel-plugin-transform-runtime.

babel преобразует синтаксис js, о котором упоминалось ранее. отasync/awaitНапример, без этого плагина (то есть по умолчанию) преобразованный код был бы таким:

// babel 添加一个方法,把 async 转化为 generator
function _asyncToGenerator(fn) { return function () {....}} // 很长很长一段

// 具体使用处
var _ref = _asyncToGenerator(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2);
});

Не зацикливайтесь на конкретном синтаксисе, просто посмотрите, это_asyncToGeneratorопределяется в текущем файле и затем используется для замены исходного кодаawait. Но каждый преобразованный файл будет вставлять абзац_asyncToGeneratorЭто приводит к дублированию и потерям.

в использованииbabel-plugin-transform-runtime, преобразованный код станет

// 从直接定义改为引用,这样就不会重复定义了。
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

// 具体使用处是一样的
var _ref = _asyncToGenerator3(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2);
});

Переход от метода определения к ссылке, тогда повторяющееся определение становится повторяющейся ссылкой, и нет проблемы дублирования кода.

Но и здесь мы находимbabel-runtimeвыходит, это совокупность этих методов, а значит,в настоящее время используетbabel-plugin-transform-runtimeкогда вы должны поставитьbabel-runtimeкак зависимость.

скажи большеbabel-runtime, который внутренне объединяет

  1. core-js: преобразовать некоторые встроенные классы (Promise, Symbolsи т. д.) и статические методы (Array.fromЖдать). Большая часть преобразования выполняется здесь. Импортируется автоматически.

  2. regenerator: в видеcore-jsпропуски, в основномgenerator/yieldиasync/awaitДве группы поддержки. при использовании в кодеgenerators/asyncавтоматически импортируется.

  3. помощники, как указано вышеasyncToGeneratorявляется одним из них, и другие, такие какjsx, classCallCheckподожди, ты увидишьbabel-helpers. Когда в коде есть встроенные помощники (как в первом фрагменте кода выше), удалите определение и вставьте ссылку (становясь таким образом вторым фрагментом кода).

babel-plugin-transform-runtime не поддерживаетсяметоды экземпляра (например,[1,2,3].includes(1))

Кроме того, существует плагин, который также может выполнять работу по разделению и объединению помощников, чтобы избежать дублирования кода, который называетсяbabel-plugin-external-helpers. Но поскольку мы используемtransform-runtimeЭта функция уже включена, поэтому ее не нужно использовать повторно. И авторы babel начали обсуждать, что эти два плагина слишком похожи, и обсуждают добавлениеexternal-helpersудалить, обсуждается вissue#5699середина.

babel-loader

Три способа использования babel были упомянуты ранее и представленыbabel-cli. Но некоторые крупные проекты будут иметь инструменты сборки (такие как webpack или rollup) для сборки и минимизации кода (uglify). Теоретически мы могли бы также использовать минимизированный код, но это было бы очень медленно. Так не было бы идеально добавить обработку Babel перед uglify?

Поэтому необходимо вставить Babel в инструмент сборки. Возьмем (я хорошо знаком) веб-пакет в качестве примера, в веб-пакете есть концепция загрузчика, поэтому он выглядитbabel-loader.

иbabel-cliТакой же,babel-loaderТакже читается в .babelrc или package.jsonbabelСегмент настраивается как собственный, и последующая обработка ядра такая же. единственное соотношениеbabel-cliСложность заключается в том, что он должен взаимодействовать с веб-пакетом, поэтому его необходимо настроить на стороне веб-пакета. Наиболее распространенные из них следующие:

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel-loader'
    }
  ]
}

Если вы хотите передать здесь элементы конфигурации babel, вы также можете изменить его на:

// loader: 'babel-loader' 改成如下:
use: {
  loader: 'babel-loader',
  options: {
    // 配置项在这里
  }
}

Элементы конфигурации здесь имеют наивысший приоритет. Но я думаю, что понятнее и читабельнее вынести его в отдельный конфигурационный файл.

Резюме

название эффект Примечание
babel-cli Разрешить командной строке использовать команду babel для переноса файлов
babel-node Разрешить командной строке использовать babel-node для прямого перевода + выполнения файлов узлов следитьbabel-cliУстановить вместе
babel-node = babel-polyfill + babel-register
babel-register переписатьrequireкоманда для перекодирования загружаемого файла без перекодирования текущего файла Только для среды разработки
babel-polyfill Добавьте совместимые методы ко всем API нужно перед всем кодомrequire, и объем относительно большой
babel-plugin-transform-runtime & babel-runtime Методы вспомогательного класса изменены с определенных перед каждым использованием на единые.require, чтобы упростить код babel-runtimeНеобходимо установить как зависимость, а не зависимость разработки
babel-loader При использовании webpack в качестве загрузчика для перекодирования перед обфускацией кода

Babel 7.x

Недавно Babel выпустил версию 7.0. Поскольку все вышеперечисленные части написаны для 6.x, поэтому давайте обратим внимание на изменения, внесенные 7.0 (без изменений в основном механизме, без изменений в плагинах, пресетах, парсинге и переводе).

Я выбираю только некоторые из них, которые больше связаны с разработчиками, и перечисляю их здесь, а большинство пропущенных относятся к определенному плагину. Полный список можно посмотретьОфициальный сайт.

Изменения в пресетах: уберите es201x, удалите stage-x и принудительно включите env (выделено)

Цель поэтапного отказа от es201x — позволить env автоматически выбирать среду, не требуя от разработчиков дополнительных усилий.Любой разработчик, использующий es201x, должен вместо этого использовать env.. А вот удаление (original deprecated) здесь не удаление, а использовать его не рекомендуется.Трудно сказать, что бабел 8 действительно удален.

Напротив, этапу-х не так повезло, их просто удалили. Это связано с тем, что команда babel считает расточительным тратить энергию на обновление пресетов для этих «нестабильных черновиков». Хотя stage-x был удален, плагины, которые он содержит, не были удалены (просто переименованы, см. следующий раздел), и мы все еще можем явно объявить эти плагины для достижения эквивалентных эффектов.Полный список

Чтобы сократить механическую работу разработчиков по замене конфигурационных файлов, Babel разработалbabel-upgradeизинструмент, который обнаружит stage-x в конфигурации babel и заменит его соответствующими плагинами. Помимо этого у него есть и другие функции, их мы подробно рассмотрим позже. (Короче говоря, цель состоит в том, чтобы сделать ваш переход на Babel 7 более плавным)

Изменения в именах пакетов npm (выделено)

Это кардинальное изменение в Babel 7, в результате которого всеbabel-*переименован в@babel/*,Например:

  1. babel-cliстал@babel/cli.
  2. babel-preset-envстал@babel/preset-env. Кроме того, можно также опуститьpresetи сокращенно как@babel/env.
  3. babel-plugin-transform-arrow-functionsстал@babel/plugin-transform-arrow-functions. иpresetТакой же,pluginтакже может быть опущен, поэтому сокращенно@babel/transform-arrow-functions.

Это изменение относится не только к зависимостям package.json, но и к конфигурации .babelrc (plugins, presets), а также для согласованности. Например

{
  "presets": [
-   "env"
+   "@babel/preset-env"
  ]
}

Кстати, ядро ​​упомянутой выше грамматики парсинга babelbabylonтеперь переименован в@babel/parser, который, по-видимому, кодифицирован.

Упомянутый выше stage-x был удален, а содержащиеся в нем плагины были переименованы, хотя и остались. Команда babel хочет более четко различать плагины, которые уже есть в спецификации (например, es2015).babel-plugin-transform-arrow-functions) и плагины, которые находятся только в черновике (например, stage-0's@babel/plugin-proposal-function-bind). Способ заключается в том, чтобы добавить к имениproposal, все подключаемые модули перевода, включенные в stage-x, используют этот префикс, кроме подключаемых модулей синтаксиса.

Наконец, если имя плагина содержит каноническое имя (-es2015-, -es3-д.), будут удалены. Напримерbabel-plugin-transform-es2015-classesстал@babel/plugin-transform-classes. (Я не использовал этот плагин один, стыдно)

Нижние версии узла больше не поддерживаются

Babel 7.0 больше не поддерживает четыре версии nodejs 0.10, 0.12, 4, 5, что эквивалентно требованию nodejs >= 6 (текущий LTS nodejs равен 8, поэтому требование не слишком велико).

Больше не поддерживается здесь означает, что babel нельзя использовать для перевода кода в этих средах узлов с низкой версией, но код, переведенный babel, все еще может работать в этих средах, так что не путайте.

Изменения только и игнорирования правил сопоставления

В вавилоне 6,ignoreопция, если включена*.foo.js, что на самом деле означает (в переводе на glob)./**/*.foo.js, который является текущим каталогомвключить подкаталогивсеfoo.jsконечный файл. Это может противоречить общепринятому пониманию разработчиков.

Итак, в Вавилоне 7 то же выражение*.foo.jsПрименяется только к текущему каталогу, а не к подкаталогам. Если вы все еще хотите работать с подкаталогами, вы должны написать это в соответствии с полной спецификацией glob как./**/*.foo.jsТолько тогда может.onlyТоже самое.

Это изменение правила применяется только к подстановочным знакам, а не к путям. такnode_modulesПо-прежнему содержит все свои подкаталоги, а не только один уровень. (Иначе девелоперы по всему миру взорвутся)

@babel/node стал независимым от @babel/cli

В отличие от babel 6, если вы хотите использовать@babel/node, его нужно установить отдельно и добавить в зависимости.

babel-upgrade

Я упомянул об этом, когда упомянул об удалении stage-xинструмент, его цель — помочь пользователям автоматизировать переход с babel 6 на 7.

Функции этого инструмента обновления включают в себя: (Это не полный список, только наиболее важный и часто используемый контент)

  1. package.json
  • поместите все зависимости (и зависимости разработки) вbabel-*заменить@babel/*
  • положить эти@babel/*Зависимые версии обновляются до последней версии (например,^7.0.0)
  • еслиscriptsв использованииbabel-node, автоматически добавлено@babel/nodeдля зависимостей разработки
  • Если естьbabelэлементы конфигурации, проверьтеpluginsиpresets, введите короткое имя (env) с полным именем (@babel/preset-env)
  1. .babelrc
  • проверить, какойpluginsиpresets, введите короткое имя (env) с полным именем (@babel/preset-env)
  • Проверьте, содержит ли онpreset-stage-x, если есть, замените его соответствующим плагином и добавьте вplugins

Он используется следующим образом:

# 不安装到本地而是直接运行命令,npm 的新功能
npx babel-upgrade --write

# 或者常规方式
npm i babel-upgrade -g
babel-upgrade --write

babel-upgradeСам инструмент все еще находится в стадии разработки, и многие TODO не выполнены, поэтому функции в будущем могут быть богаче, например, упомянутые выше.ignoreпреобразование подстановочных знаков и т. д.