Пришло время полностью освоить импорт / экспорт

Благодаря полной поддержке модулей JavaScript во всех основных браузерах, опытные разработчики теперь могут создавать готовые к работе приложения без Webpack.

Front-end веб-разработчики находятся на пороге нового способа запуска своих приложений в производство. Потребность в транспиляторах и сборщиках исчезает теперь, когда браузеры наконец-то получили поддержку import и export с использованием модулей, совместимых со стандартами ECMA-262.

До сих пор последний выброс, Microsoft Edge, удерживал нас от развертывания модулей ES в «дикой природе». Сегодня, с выпуском Edge 76 на базе Chromium, все основные браузеры поддерживают новый стандарт модулей. Это имеет огромное значение для того, как мы создаем и развертываем наш код.

Чтобы понять мою точку зрения, позвольте мне поделиться некоторыми историческими событиями.

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

Yahoo! Эра

В 2005 году Yahoo опубликовала статью, которая впоследствии стала чем-то вроде библии по интерфейсу. Оглядываясь назад из сегодняшнего звездного созвездия, Yahoo кажется тускло освещенной пылинкой. Но в рейтинге журнала Time «15 самых влиятельных веб-сайтов всех времен» Yahoo! входит под номером семь.

Статья, которая вызвала всеобщее оживление, называлась «Лучшие методы ускорения работы вашего веб-сайта». Вверху этого списка было его золотое правило минимизировать HTTP-запросы. Он дал однозначный совет, как этого добиться: объедините все свои скрипты в один файл.

В то время объединение ваших сценариев было простой заповедью. Большинство из того, что мы написали, было простыми прослушивателями событий, которые управляли пользовательским интерфейсом. Более сложные веб-сайты могут иногда писать функцию, которая запускает XMLHttpRequest для получения данных.

В любом случае, независимо от того, что мы писали в ту эпоху, все это было помещено в глобальную область видимости. Поэтому, когда наши веб-сайты стали еще более интерактивными, использование глобального обзора становилось все более проблематичным. Как назвать функцию, которая выполняет итерацию по списку вещей, и как отделить это имя от других похожих? Такие имена, как iterateThing1() и iterateThing2(), не могли быть неслыханными.

В то время у меня был один клиент, вся кодовая база которого в 20 000 строк находилась в одном файле. Все разработчики в команде работали над ним одновременно! Что-то нужно было отдать.

Эра AMD и CommonJS

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

Вместо этого появились решения на уровне приложений в виде спецификации определения асинхронного модуля (AMD) и RequireJS реализации этого. В этом решении все в файле было заключено в свой собственный контекст, изолируя его функции и переменные извне, эффективно предотвращая конфликты имен. Каждый файл был отдельной единицей. Чтобы использовать этот модуль, разработчик должен выбрать, что сделать видимым для внешнего мира, заключив цель в define() функцию. И любой другой модуль, который хотел бы получить к нему доступ, сделал бы это с помощью функции require().

Пока это происходило во внешнем интерфейсе, Node.js боролся с той же проблемой в серверной части. Их решением стала модульная система CommonJS. В этой системе разработчик мог выбирать, что сделать видимым для внешнего мира, объявив module.exports statement. Любой другой модуль, который хотел бы получить доступ к этим экспортам, сделал бы это с помощью оператора require.

Два разных решения одной и той же проблемы.

Перенесемся в 2015 год, когда комитет ECMAScript TC39 вступил в бой и официально объявил о новых языковых функциях, которые напрямую удовлетворяли потребность в модульном коде. Они объявили, что модули будут иметь первоклассную языковую поддержку благодаря двум новым ключевым словам: import и export. Это положило конец спору.

Но объявить что-то и реализовать это - разные вещи. Только в конце 2017 года появилась первая реализация в браузере Chrome. Для полной поддержки import/export во всех основных браузерах потребуется еще два с половиной года.

Полную историю модулей AMD, CommonJS и ES рассказывать здесь не нужно. Но последствия, проистекающие из длительного внедрения import/export, важны для моей истории. Последствия заключались в том, что разработчикам, желающим воспользоваться преимуществами модульного кода, приходилось выбирать: либо писать с использованием старого синтаксиса модуля, либо писать с новым синтаксисом и транспилировать его обратно к старому синтаксису для развертывания.

Для своих проектов я решил писать с новым синтаксисом. А чтобы развернуть код в производственной среде, я использовал специально созданный инструмент CLI, который я назвал eximjs, который преобразовывал операторы import и export в операторы CommonJS. Когда я писал этот инструмент, я думал, что у него будет недолгая жизнь. Я не ожидал, что буду использовать его в течение пяти лет!

Эпоха бандлеров

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

Чтобы заставить эту работу работать, разработчики интерфейсов начали отходить от простых рабочих процессов сценариев, на которых они выросли, и начали переходить к процессу сборки. Это вызвало потребность в средствах выполнения задач: инструментах, которые могли бы работать как последовательность шагов. Изначально эту потребность выполняли Grunt и Gulp. Мы использовали их для создания процессов сборки, которые транспилировали, линзовали, тестировали и минимизировали файлы исходного кода всякий раз, когда они менялись.

Модульные архитектуры стали реальностью, и разработчики пошли ва-банк. Проблема пространства имен была решена.

Ребята из DevOps первыми заметили, что этот новый подход создает собственный набор проблем. Или, скорее, он вновь поднял на поверхность старую проблему, которую Yahoo так тщательно определила много лет назад. Множество маленьких файлов означало множество HTTP-запросов, что означало более длительное время загрузки. Но мы не собирались отказываться от создания модульных архитектур только для этого. Нам нужен был другой подход.

Новый подход появился в виде сборщиков пакетов. Используемые нами обработчики задач были заменены новыми инструментами, которые анализировали наш JavaScript и определяли его зависимости. Эти зависимые модули были объединены в связку и отправлены по сети в одном HTTP-запросе. DevOps был доволен.

Все переделали свой процесс сборки, отбросив средства выполнения задач и заменив их сборщиками, такими как Browserify или Webpack. Вскоре после этого конкуренция пришла в виде Parcel, который избавил от значительной части накладных расходов на настройку, необходимых для Webpack, и Rollup, который сосредоточился на изоморфном JavaScript и желании объединить модули ES.

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

Но во всем этом есть что-то неладное. Первоначальной проблемой была проблема блокировки заголовка строки HTTP, которую Yahoo так осторожно назвал. Затем это превратилось в проблему пространства имен, поскольку наши приложения стали более сложными. Затем возникла проблема нехватки языка, поскольку мы ждали, пока TC39 точно укажет, как должен работать новый загрузчик модулей. А тем временем это стало проблемой производительности сборщика, поскольку мы пытались сбалансировать все конкурирующие потребности для более быстрой транспиляции, загрузки по запросу и кэширования.

Эра 2020 года

Но давайте сделаем передышку и еще раз рассмотрим эти проблемы в более широком контексте всего веб-стека. В частности, давайте посмотрим на них в контексте HTTP.

Даже в то время, когда разворачивалась драма модулей, решение нашей первоначальной проблемы с производительностью появлялось в виде HTTP / 2. Это обновление классического веб-протокола, которое мы все так хорошо знаем, обеспечивает два основных улучшения, которые важны для моей истории: постоянные соединения и мультиплексированные потоки.

Вот как это работает. В HTTP / 2 запрос HTML-документа открывает соединение между браузером и сервером, которое остается открытым до тех пор, пока не будет доставлено все необходимое. Более того, когда браузер обнаруживает, что ему нужно (изображения, шрифты, скрипты, стили), он запрашивает их, не останавливаясь и не дожидаясь полного выполнения ответа. Это работает, потому что новый протокол имеет мультиплексированные потоки, поэтому все эти запросы могут выполняться одновременно. Короче говоря, проблема блокировки заголовка становится спорной.

Теперь позвольте мне сделать небольшое отступление. Услышав о HTTP / 2, я, естественно, искал веб-сервер, который поддерживал бы новый протокол вместе с библиотеками Node.js, которые я писал. Я не был доволен ничем, что видел в то время. Это стало моим катализатором создания сервера HTTP / 2 Служба чтения и записи. Сегодня я использую этот сервер в каждом новом проекте и могу подтвердить преимущества нового протокола. Постоянные соединения и мультиплексированные потоки доставляются в соответствии с объявлением.

Этот новый протокол напрямую связан с моим принципом, что сборщики пакетов скоро станут пережитком прошлого. Поскольку HTTP / 2 прекрасно вмещает множество файлов, приложениям не нужно отдельно обрабатывать какую-либо часть этой оптимизации.

Преимущества, предоставляемые HTTP / 2 вместе с собственными модулями ES, многочисленны:

  • Транспиляция больше не требуется при использовании операторов JavaScript import и export language.
  • Карты исходного кода больше не требуются для отладки, поскольку исходный код не искажается транспилером.
  • Чтобы угадать оптимальные границы блоков, не требуется сложной эвристики, потому что блоки - это не вещь.
  • Для разделения пакетов не требуется административный код, поскольку модули поступают в виде файлов с отдельными именами.
  • Оптимизатор отложенной загрузки не нужен, потому что браузер запрашивает скрипты и ресурсы только тогда, когда они ему нужны.
  • Встряхивание дерева не требуется, потому что браузер запрашивает только обнаруженных им иждивенцев.
  • Кэширование браузером, сервером и сетью доставки контента может полностью использовать HTTP-заголовки cache-control, if-none-match и etag, экономя пропускную способность сети и повышая пропускную способность.
  • Сжатие файлов может быть настроено персоналом DevOps, используя лучший подход, доступный для каждого типа mime.

По сравнению с HTTP / 1.1 с сборщиками, HTTP / 2 с собственными модулями ES является явным победителем. Короче говоря, файлы исходного кода можно отправлять прямо в производство, без всего того безумия, которое мы создали для себя.

Мы так долго относились к JavaScript как к компилируемому языку, что забыли, что на самом деле это интерпретируемый язык. Да, мы все еще можем использовать линтеры и минификаторы, чтобы поддерживать порядок, но все остальные манглеры просто контрпродуктивны.

Короче говоря, если у вас есть какое-либо влияние на выбор инструментов сборки вашей командой, сейчас хорошее время, чтобы переосмыслить, зачем вам нужен Webpack.

Простота - лучший способ добиться успеха.