В последнее время на Хабре появилось пара интересных статей. Первая была посвещена проблеме минификации ES6, вторая про общие полезные советы оптимизации webpack.
Все бы хорошо, но они обе обошли стороной вопрос разделение бандлов на ES6 и ES5 для целей минификации и другой оптимизации. И вообще, в то время как одни все пишут и пишут статьи про это — другие (почти все) данную технику игнорируют.
Потому что Долго. Дорого. И не так чтобы очень очень.
А надо быстро, дешево, и потупее. Возможно следует просто обратить эволюцию вспять.
Идея
Описывать «идею» — не самая лучшая идея. Лучше описать то, как она должна работать. То как процесс формирования бандла должен работать:
- у меня есть код
- я его компилирую под мой «разработческий» браузер
- и оно все работает.
Разработческий браузер тут — так чтобы async/await, generator, classes, arrow functions и так далее. В общем target: esmodules в бабеле.
Не знаю как вам, а мне такая идея нравится. Вот только старым браузерам, которые все еще среди нас, эта идея не так чтобы заходит. (и потому мы все шипим es5 в продакшен, приправив полмегабайтом полифилов)
И именно это надо исправить.
Devolution
Devolution — маленькая cli утилитка, которая возьмет ваш бандл, скомпилированный в target: esmodules, и деградирует его до es5, добавив все нужные полифилы по пути.
Если вкратце, то:
- находятся все js скрипты
- прогоняются через babel с одним активным плагином (форк useBuiltins: «usage»), который определяет требуемые полифилы. Это быстро, так как трансформаций нет.
- для каждого файла собираются все нужные ему полифилы (минус те, что уже есть в главном бандле), обьединяются, прогоняются через terser и добавляются в начало файла.
- каждый файл прогонятся через swc, rust версию babel, которая де-модернизирует код до уровня понятному IE11. Работает в 10-60 раз быстрее babel. Он не поддерживает различные плагины, но это и не нужно — все что нужно __уже__ применено.
- на результат еще раз накладывается terser, но с выключеным mangle(сжатие имен), что опять же — быстро.
- все это выполняется в воркерах.
Я прогнал код на трех проектах разного уровня сложности:
- проект 1, 60 конечных js файлов (code-splitting). Время сборки 400s. Devolution 30s.
- проект 2, 1 конечный js файл (30mb). Время сборки 120s. Devolution 10s.
- проект 3, 1 конечный js файл (2mb). Время сборки 20s. Devolution 5s (на старте воркеров много что-то да и теряется).
Бонус от ESM бандла получился немного странный:
- один проект похудел на 400kb babel/polyfill. Банально там не использовалось ничего «сверх» браузерных фишек, и в «esm» их полифилить не надо
- один проект похудел на 10% из-за сильно более компактного кода генераторов, async/await и конструкторов классов
- один проект потолстел, так как «loose» babel трансформации иногда делают код более компактным. Но loose mode немного опасная опция, в то время как «ES6» код — «безопасный».
Еще раз:
- берем ES6 код (точнее esmodule, let/const будут заменены на var в целях скорости)
- делаем из него ES5
- докидываем сбоку полифилов
- раскидываем по папочкам, добавляем симлинки на остальные файлы
- меняем подключение скриптов на страницы на чуть более умное (IE11 modules/nomodules не понимает)
- готово — ESM для 85% кастомеров, ES5 для тех кто в танке.
Просто. Быстро. Просто Тупо. Мы де-модернизировали бандл. Старые браузеры! Ау — кушать подано.
Ну а новые браузеры получат бандл почти без полифилов, без страшных трансформаций генераторов и async/await, с arrow functions без бубнов (и они вообще быстрее). В общем все счастливы, вроде так изначально и задумывалось.
github.com/thekashey/devolution
PS: На самом деле в данный момент devolution не использует swc, так как он иногда делает код не очень рабочим — github.com/swc-project/swc/issues/280, Бабель при этом не так чтобы сильно медленнее — там где swc правлялся за 20 секунд, babel справляется за минуту. При времени «нормальной» сборки — от 5ти и далее — это большой плюс
P.S.: Если вдруг стало интересно почему devolution — видео здесь.
Автор: Корзунов Антон