DEV Community

Cover image for Почему и как нужно транспилировать зависимости
Dan Onoshko
Dan Onoshko

Posted on • Edited on • Originally published at github.com

4 2

Почему и как нужно транспилировать зависимости

Если вы разработчик сайта, то наверняка пользуетесь каким-либо сборщиком (например, Webpack, Rollup или Parcel), который к тому же транспилирует ваш JavaScript-код с помощью Babel. Ну и, конечно же, вы наверняка используете разнообразные зависимости, чтобы сократить время разработки.

Обычно транспилировать код зависимостей не принято, ведь вроде как и без этого все прекрасно работает. Но времена меняются…

(В этой статье я делюсь своим опытом работы над open source проектами. Это и мое хобби, и работа в Cube, где мы создаем open source инструменты для создания приложений для работы с данными.)

Распространение ESM

До появления нативной поддержки ES-модулей в браузерах и в Node.js, npm-пакет мог содержать несколько вариантов кода:

  • CommonJS-вариант без использования новых фишек JavaScript, таких как стрелочные функции. Такой код совместим с большинством версий Node.js и браузеров. Файл этого варианта указывают в поле main в package.json.
  • module-вариант, использующий ES6-импорты и экспорты, но так же без использования новых фишек. Модули позволяют делать tree-shaking, то есть не включать в бандл неиспользуемый код. Файл этого варианта указывают в поле module в package.json.

Конечно, не все npm-пакеты сделаны по такому принципу — все зависит от разработчика. Однако браузерные и универсальные библиотеки часто распространяются именно так.

Сейчас ES-модули нативно поддерживаются браузерами уже больше трёх лет, а в Node.js их поддержка появилась в версии 12.20, выпущенной в ноябре 2020 года. Разработчики библиотек стали включать в npm-пакеты еще один вариант кода — для сред, нативно поддерживающих ES-модули, или вовсе перешли на поддержку только ES-модулей.

Особенности ESM

Важно понимать, что нативные ES-модули — это не то же самое, что модули, использующие ES6-синтаксис импорта и экспорта:

  • То, как мы привыкли использовать import / export, не будет нигде работать нативно — такой код предназначен для дальнейшей обработки бандлером или транспайлером.

  • Нативные ESM не умеют резолвить расширения и index-файлы, поэтому их нужно явно указывать в пути:

    // Неправильно:
    import _ from './utils'
    
    // Правильно:
    import _ from './utils/index.js'
    
  • Если пакет содержит ESM-код, то в package.json нужно явно указать тип пакета с помощью "type": "module".

  • Для указания файла с ESM в package.json нужно использовать поле exports.

Более подробно про особенности ESM можно прочитать в этой статье.

Поддержка JavaScript

Еще одна особенность ESM это то, что мы точно знаем, с каких версий браузеры и Node.js поддерживают ES-модули. Соответственно, мы точно знаем, какие фишки JavaScript мы можем использовать в коде.

Например, все браузеры с нативной поддержкой ES-модулей имеют поддержку стрелочных функций, а значит нам больше не нужно избегать их использования или настраивать Babel для их транспиляции в обычные функции. Разработчики библиотек пользуются этой особенностью и используют в ESM-коде все новые фишки JavaScript.

Транспиляция зависимостей

Но что делать, если ваш сайт должен работать в более старых браузерах? Или если какая-нибудь из зависимостей использует новые фишки JavaScript, не поддерживаемые актуальными браузерами?

Правильно! В обоих случаях нужно делать транспиляцию зависимостей.

Транспилируем вручную

Разберем на примере конфигурации webpack в паре с babel-loader. Типичный пример выглядит примерно так:

module: {
  rules: [
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env', { targets: 'defaults' }]
          ]
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

В документации и примерах использования Babel и babel-loader рекомендуют исключать node_modules из файлов для транспиляции (exclude: /node_modules/), чтобы сборка выполнялась быстрее. Удалив эту строчку, мы включим транспиляцию зависимостей, но пожертвуем скоростью сборки. Есть компромиссный вариант: если мы знаем, какие именно зависимости нужно транспилировать, то можем указать только их:

exclude: _ => /node_modules/.test(_) && !/node_modules\/(nanostores|p-limit)/.test(_)
Enter fullscreen mode Exit fullscreen mode

Или можем выбрать только файлы с определённым расширением:

exclude: _ => /node_modules/.test(_) && !/(\.babel\.js|\.mjs|\.es)$/.test(_)
Enter fullscreen mode Exit fullscreen mode

Как будет различаться размер бандла и время сборки с разными настройками? Рассмотрим на примере бандла с тремя очень разными зависимостями:

  • p-limit (использует самые последние фишки JavaScript, включая приватные поля класса, которые поддерживаются не везде)
  • axios (ES5-код)
  • и svelte (использует актуальные фишки JavaScript, такие как стрелочные функции)
Конфигурация Транспиляция Совместимость Размер бандла Время сборки
Базовая Не выполняется Невозможно предсказать, с какими браузерами совместим бандл 21 КБ  1,8 с 
target: defaults and supports es6-module До ES6. Приватные поля классов будут даунгрейднуты, стрелочные функции и классы останутся Новые браузеры 22 КБ  2,6 с 
target: defaults с полифилами До ES5 Все браузеры 123 КБ  6,1 с 

Общее время сборки двух бандлов с помощью babel-loader составило 8,7 с. (Надеюсь, понятно, что в нашем примере, без транспиляции, получившийся бандл не будет совместим со старыми браузерами из-за p-limit.)

(Кстати, про сборку нескольких бандлов под разные браузеры подробно написано в другой моей статье.)

Но что делать, если вам не хочется вручную указывать нужные файлы и пакеты в конфигурации? Есть готовый и очень удобный инструмент!

Транспилируем с помощью optimize-plugin

optimize-plugin для webpack от Джейсона Миллера из Google (@_developit) сделает за вас всё, что нужно, и даже чуть больше:

  • оптимизирует ваш код и код всех зависимостей
  • если нужно, сгенерирует два бандла (для новых и старых браузеров), используя подход module/nomodule
  • а ещё может сделать апгрейд ES5-кода до ES6, используя babel-preset-modernize!

Вот какие результаты будут у optimize-plugin для нашего бандла с тремя зависимостям:

Конфигурация Транспиляция Совместимость Размер бандла Время сборки
Базовая Одновременно до ES6 и до ES5 с полифилами Все браузеры 20 КБ для новых браузеров
92 КБ для старых браузеров (из них 67 КБ — полифилы)
7,6 с 

Общее время сборки двух бандлов с помощью optimize-plugin составило 7,6 с. Как видно, optimize-plugin не только быстрее babel-loader, но и создаёт бандл меньшего размера. Можете проверить сами.

Почему optimize-plugin выигрывает

Выигрыш в скорости получается за счёт того, что код анализируется и собирается не два раза, а один, после чего optimize-plugin транспилирует получившийся бандл под новые и старые браузеры.

Получить выигрыш в размере позволяет babel-preset-modernize. Если в своём коде вы, скорее всего, используете все фишки ES6+, то в зависимостях может быть всё что угодно. Поскольку optimize-plugin работает с уже собранным бандлом, который содержит код всех зависимостей, их код тоже будет транспилирован.

Вот пример работы babel-preset-modernize. Предположим, мы написали такой код:

const items = [{
  id: 0,
  price: 400
}, {
  id: 1,
  price: 300
}, {
  id: 2,
  price: 550
}];
const sum = items.reduce(function (sum, item) {
  const price = item.price;
  return sum + price;
}, 0);

console.log(sum);
Enter fullscreen mode Exit fullscreen mode

После транспиляции получим:

const items = [{
  id: 0,
  price: 400
}, {
  id: 1,
  price: 300
}, {
  id: 2,
  price: 550
}];
const sum = items.reduce((sum, {
  price
}) => sum + price, 0);

console.log(sum);
Enter fullscreen mode Exit fullscreen mode

Что поменялось:

  • обычная анонимная функция заменена на стрелочную функцию
  • доступ к свойству item.price заменён деструктуризацией аргумента функции

Размер кода уменьшился с 221 байта до 180 байт. При этом здесь выполняется всего два типа трансформаций, но babel-preset-modernize умеет делать больше.

Что дальше?

Плагин демонстрирует очень крутые результаты, но ему ещё есть, куда расти. Недавно я сделал несколько улучшений, в том числе добавил поддержку webpack 5.

Если вас заинтересовал optimize-plugin, то призываю попробовать его для сборки своих приложений, а также внести свой вклад в его развитие.

В любом случае, транспилируйте зависимости своего кода, чтобы контролировать его совместимость со старыми и новыми браузерами.

Кроме того, если вы создаете приложение для работы с данными, обратите внимание на Cube. Он поможет вам создать API для метрик, которые вы сможете использовать в своем приложении за считанные минуты.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more

Cloudinary image

Optimize, customize, deliver, manage and analyze your images.

Remove background in all your web images at the same time, use outpainting to expand images with matching content, remove objects via open-set object detection and fill, recolor, crop, resize... Discover these and hundreds more ways to manage your web images and videos on a scale.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay