Если видео недоступно для просмотра, попробуйте выключить блокировщик рекламы.

Итак, мы знаем, что есть спецификация, а есть ее реализация. Знаем, что реализация зачастую отстает от спецификации. Более того, разные реализации по-разному отстают от спецификации. Написав код, мы не можем гарантировать, где он будет запускаться, а где — нет.

Исходя из этого можно сделать вывод, что нужно писать код, придерживаясь старых стандартов. К счастью, есть другой путь: мы можем писать код с использованием всех возможных фич, но перед публикацией автоматически транслировать его (то есть переводить из одного вида в другой) в старую версию. Звучит сложно, но на практике все просто.

Сама природа JS и его способы использования готовят нас к тому, что никогда не настанет светлых времен с современными рантаймами (средами исполнения, интерпретаторами js-кода). Люди использовали и продолжат использовать разные браузеры и разные версии браузеров, разные версии Node.js и так далее. Использование новых синтаксических конструкций в такой ситуации практически невозможно. Запуск кода на платформе, не поддерживающей новый синтаксис приведет к синтаксической ошибке. Закономерным решением этой проблемы стало появление Babel — программы, которая берет указанный код и возвращает тот же код, но транслированный в старую версию JS. Фактически, в современном мире Babel стал неотъемлемой частью JS. Его не используют только в старых проектах, также называемых легаси-проектами. Все новые проекты так или иначе делают с его использованием.

У Babel есть собственный онлайн REPL. Попробуйте вставить туда любой код, который вы писали на Хекслете, и посмотрите, во что он превратится. Такая трансляция называется транспайлингом, а сам Babel называют транспайлером, от transpiler.

// Before
const factorial = (n) => {
  if (n === 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}
// after
"use strict";

var factorial = function factorial(n) {
  if (n === 1) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
};

Babel состоит из многих частей:

  • Пакет @babel/core содержит код, который выполняет всю работу по трансляции, но не содержит внутри себя правил преобразования. Правила описаны в отдельных пакетах, называемых плагинами (например, babel-plugin-transform-constant-string).
  • @babel/preset-env. Пресет - это группа плагинов, которую можно подключить к Babel целиком. preset-env - основной пресет поддерживаемый командой Babel, который содержит внутри себя плагины, реализующие стандартизированные возможности js.
  • Пакет @babel/cli обеспечивает возможность работы с бабелем через терминал. Предоставляет командную утилиту babel. Ниже рассматривается ее использование.
  • Пакет @babel/node - еще одна утилита командной строки: babel-node. Подробнее далее.

Установка

$ npm install --save-dev @babel/core @babel/cli @babel/node @babel/preset-env

Настройка

Babel полагается на наличие файла babel.config.js в корне проекта. Именно через него он узнает, как нужно транслировать код.

Этот файл нужно создать самостоятельно. Если вы забудете добавить туда плагин или пресет, то на выходе Babel отдаст тот же код, что был и на входе.

module.exports = {
  presets: [
    ['@babel/env', {
      targets: {
        node: 'current',
        firefox: '60',
        chrome: '67',
        safari: '11.1',
      },
    }],
  ],
};

Разные среды исполнения поддерживают (или не поддерживают) разные возможности и синтаксические конструкции языка. В свойстве targets перечисляются конкретные окружения (и их версии), для которых пишете код. Если код предназначен для выполнения на nodejs, то достаточно указать только его. В таком случае babel будет транслировать конструкции, поддерживаемые на nodejs, и ничего лишнего:

module.exports = {
  presets: [
    ['@babel/env', {
      targets: {
        node: 'current',
      },
    }],
  ],
};

Минимально достаточно подключить пресет @babel/preset-env. Он добавляет возможности JS, которые входят в стандарт.

Использование

При появлении в проекте Babel, изменяется файловая структура проекта. Так как код существует в двух состояниях: исходном и транслированном, то удобно создать две директории, под каждый набор исходников. Исходный код, принято хранить в директории src в корне проекта, а код полученный в результате трансляции - в директории dist.

# Команда babel предоставляется пакетом @babel/cli
$ npx babel src --out-dir dist

Эта команда берет весь код из файлов в директории src и создает его транслированную версию в директории dist. Запускается он точно так же, как и любой другой код, и фактически именно этот код нужно доставить в NPM-репозиторий. Другими словами, пользователи вашего пакета запускают код из директории dist, а не src, хотя сами об этом не знают. Сама директория dist добавляется в .gitignore, так как сгенерированный код нужен только в момент публикации пакета для упаковки в архив, который уходит в NPM-репозиторий. В процессе разработки пакета, запуск сборки не требуется.

Есть только один маленький нюанс. Изначально я сказал, что NPM никак не интегрирован с Git, но это не совсем правда. По умолчанию NPM смотрит в файл .gitignore. Все, что там перечислено, не попадет в NPM-репозиторий при публикации пакета. В нашем случае такой директорией является dist, но именно ее мы и хотим опубликовать. Выходов из этой ситуации несколько. Один связан с файлом .npmignore и описан в документации, про другой я скажу подробнее. NPM позволяет указать список файлов и папок, которые нужно опубликовать. Достаточно добавить секцию files в package.json. Содержимое files — массив директорий и файлов:

"files": [
  "dist"
]

Существует два способа подготовки пакета к публикации. Первый подход заключается в том, чтобы перед выполнением npm publish вручную сгенерировать каталог dist, используя скрипты: npx babel src --out-dir dist. Подход рабочий, но сопряжён с постоянными ошибками в стиле "ой, забыл собрать новый код". К тому же, это действие может быть автоматизировано — именно эту идею и реализует второй подход. NPM содержит множество предопределённых скриптов, которые выполняются автоматически в определённые этапы работы. Например, prepublishOnly запускается перед непосредственным выполнением публикации. То, что нам и требуется.

"scripts": {
  "build": "NODE_ENV=production babel src --out-dir dist",
  "prepublishOnly": "npm run build"
}

Если у вас Windows, вам понадобится утилита cross-env.

В примере выше используется небольшой трюк. В prepublishOnly вызывается другой скрипт — build. Этот приём используется широко, и он действительно удобен. Бывают ситуации, когда все же нужно запускать сборку руками. Поэтому удобно иметь отдельную команду только для генерации. Скрипт build как раз и призван решить эту задачу.

Подчеркну еще раз: каталог dist не должен храниться в git-репозитории, и вы не найдете его на Гитхабе. Посмотрите lodash. Она генерируется только в момент публикации пакета и заливается в npm-репозиторий. Каждая новая публикация должна генерировать этот каталог заново. Только в этом случае обновится код в пакете.

Подведём итог. В git-репозитории хранится исходный код, ещё не обработанный babel. Это значит, что вы всегда можете найти библиотеку и изучить её содержимое на github. А вот пакет, установленный к вам в систему содержит обработанный код, предназначенный для запуска, а не для чтения. Этот код не хранится в git-репозитории. Он попадает в NPM-репозиторий в момент публикации новой версии пакета за счет выполнения команды prepublishOnly (в которую вы сами должны прописать вызов трансляции).

Babel-node

При использовании новых возможностей js, запуск кода на выполнение node file.js, упадет с ошибкой, потому что внутри файла используется синтаксис, который нода не понимает. Для запуска кода после каждого изменения, необходимо выполнять трансляцию. Этот процесс выглядит так:

  1. Делаем изменение.
  2. Транслируем код с помощью Babel.
  3. Запускаем на выполнение.

Разработчики Babel предусмотрели эту ситуацию. В этом случае можно установить пакет @babel/node. Теперь код можно вызывать так: npx babel-node src/index.js. Команда babel-node делает одновременно две вещи. Транслирует код и сразу же запускает его на выполнение. В отличие от команды babel, babel-node не сохраняет результат трансляции. Все происходит во время работы в памяти. Обратите внимание на то, что вам все равно понадобится правильно настроенный файл babel.config.js в корне проекта иначе babel-node не сможет произвести трансляцию и так же завершится с ошибкой синтаксиса на момент запуска.

Самостоятельная работа

Попробуйте выполнить скрипт build в пакете nodejs-package. Изучите результаты его работы в папке dist. Вы должны увидеть, что содержимое файлов внутри dist отличается от содержимого тех же файлов внутри src. Вместо const использован var, вместо import - require. В целом код остается читаем, хотя и выглядит странновато.


Дополнительные материалы

  1. Babel Node - описание способов запуска из командной строки
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →