HTTP/2 вышел! CDN — это горячо!
Возможно, вы слышали о HTTP/2 и его мультиплексировании активов, а также о преимуществах, которые он может принести вашему проекту, позволяя загружать больше активов одновременно, уменьшая необходимость в массивных пакетах активов и тем самым снижая отток кэша.
Все вышесказанное — правда. HTTP/2 сотворил чудеса с загрузкой активов. Однако HTTP/2 не является универсальным решением, позволяющим отказаться от текущего набора фронтенд-пакетов и использовать только CDN.
Однако проблема в том, что вы теряете ряд преимуществ, которые можно получить, используя frontend bundler. Для выполнения этого упражнения мы рассмотрим Shoelace.
Shoelace — это проект с открытым исходным кодом, который использует веб-компоненты и переменные CSS для создания целостной системы дизайна вашего веб-приложения. Этот пример не говорит о том, что Shoelace работает или не работает, а просто используется в качестве примера того, где CDN падают. Кроме того, вы должны использовать Shoelace, это очень крутой проект!
Приступим
Самый простой путь установки Shoelace — это импорт из JS CDN, например jsdelivr.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.78/dist/themes/light.css" />
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.78/dist/shoelace.js"></script>
Давайте посмотрим на созданный сетевой график.
Не погружаясь слишком глубоко, можно сказать, что Shoelace поставляет базовый «модуль», который затем использует ряд общих блоков, генерируемых ESBuild. Поэтому, когда запрашивается <script src="${cdn}@{versionNumber}/dist/shoelace.js"
, то запрашиваются чанки, которые также находятся на том же пути, и эти чанки могут импортировать другие чанки, а затем эти чанки импортируются. Вы можете видеть, как при наличии глубоко вложенного набора «чанков» это может быстро увеличить время отклика и создать эффект «водопада», который мы видим здесь. Водопад» называется так потому, что каждый запрос зависит от предыдущего запроса. Представьте, что у вас несколько редиректов
на одном маршруте.
redirect_to "/foo" -> redirect_to "/bar" -> redirect_to "baz"
Это тот же самый эффект. Вы не знаете, что вам нужен следующий актив, пока не запросите этот актив. Или, в данном случае, файл javascript.
Устранение водопада
Введите <link rel="modulepreload">
.
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload
Ссылка предварительной загрузки модуля позволяет вам запросить актив, не дожидаясь, пока браузер перейдет по цепочке перенаправлений на актив. Хотя это и хорошо, но очень трудно сделать это правильно.
Давайте рассмотрим https://jspm.org/, которая является CDN по умолчанию, используемой с новыми Rails importmaps.
Я создал граф «modulepreload» для Shoelace-beta.78 и загрузил его в свой index.html.
https://generator.jspm.io/#U2NjYGBkDM7IT81JTE5VyMwtyC8qyU0sYHAohorpFpdU5qTqw7gORnoGega6SakliXrmFgB/h42GPwA
Вы можете видеть, что этот «водопад» немного меньше, и начальные «куски» запрашиваются параллельно, что уменьшает эффект «водопада»! Предварительная загрузка модулей — это хорошо, и она отлично работает в Shoelace! Однако, они добавляют много накладных расходов, поскольку каждый раз, когда вы обновляете версию Shoelace, вам приходится генерировать новый граф предзагрузки.
Давайте также подумаем о том, что по мере добавления все новых и новых зависимостей секция предварительной загрузки модуля может стать довольно большой!
Когда вы используете бандлер, выполняется статический анализ кода, и все ваши активы могут быть предварительно загружены в один единственный бандл, и этот анализ кода намного глубже и намного сложнее, чем простой импорт файлов, разрешенный предварительной загрузкой модулей.
Неиспользуемая подстановка импорта
В традиционных модулях JavaScript, импортируемых из CDN, нет никакого древовидного импорта. Что вы запрашиваете, то и получаете. Такие сборщики, как Parcel / Rollup / Webpack / Vite, могут статически анализировать любой код, который вы импортируете, и избавляться от всех путей кода, которые не используются. CDN не имеют такой привилегии, поскольку у них нет возможности анализировать ваш конечный код.
Вот очень простой пример того, как работает древовидная система на основе зависимостей.
export function doTheRoar () { return "roar" }
export function doTheMeow () { return "meow" }
Затем в коде нашего приложения мы сделаем примерно следующее:
import * as doIt from "./my-package"
console.log(doIt.doTheRoar())
В приведенном выше случае пакетник избавится от функции «doTheMeow» и отправит только «doTheRoar» в наш окончательный производственный пакет, тем самым уменьшив количество JavaScript, которое мы отправляем. Если мы используем CDN, doTheRoar
и doTheMeow
всегда будут поставляться, даже если они не используются.
Подъем объема
Пока мы говорим об эффективности, мы можем поговорить о «подъеме области действия». Поднятие области видимости эффективно следующим образом из документации Parcel:
…объединяет модули в одну область видимости, когда это возможно, вместо того, чтобы оборачивать каждый модуль в отдельную функцию. Это называется «поднятие области видимости». Это помогает сделать минификацию более эффективной, а также улучшает производительность во время выполнения, делая ссылки между модулями статическими, а не динамическими поисками объектов.
https://parceljs.org/features/scope-hoisting/
Scope hoisting помогает минимизировать объем JavaScript, который мы поставляем, а также повышает производительность. По-моему, звучит неплохо!
Разбивка, разделение кода и дублирование кода
Пакеты для фронтенда позволяют нам «разбить» или «разделить код» наших пакетов и поставлять меньшие пакеты, используя преимущества HTTP/2! Мы можем разделять код различными способами, о которых я не буду говорить, но суть в том, что вы можете это делать. Разделение кода позволяет нам создавать оптимизированные куски.
Из документации Parcel:
…позволяет разделить код приложения на отдельные пакеты, которые могут быть загружены по требованию, что приводит к уменьшению размера начального пакета и ускорению загрузки.
https://parceljs.org/features/code-splitting/
Это означает, что мы можем получить чанки для каждой страницы и делать такие причудливые вещи, как «ленивая загрузка» некритичных зависимостей. Да, вы можете сделать это с помощью обычных модульных скриптов, но это гораздо больше ручной работы.
Разбивка на куски/разделение также позволяет говорить о дублировании зависимостей. С помощью CDN вы можете легко увеличить количество импортируемого JavaScript, если импортируете похожие, но немного отличающиеся по версии зависимости.
Например, Shoelace импортирует Lit, и допустим, вы хотите использовать Lit в своем проекте. Из-за того, что Shoelace импортирует Lit внутри CDN, чтобы сделать его самодостаточным, вы не получаете доступа к Lit, а значит, вам придется импортировать библиотеку заново самостоятельно, что означает, что она не является «общей» зависимостью.
https://unpkg.com/browse/@shoelace-style/shoelace@2.0.0-beta.78/dist/chunks/chunk.WWAD5WF4.js
С помощью frontend bundlers мы можем легко подключиться к lit и самостоятельно собрать исходники Shoelace, что позволит нам поставлять меньший конечный пакет за счет отсутствия дублирования зависимостей.
Совместное использование кэша на разных сайтах и доменах
Последнее замечание касается кросс-оригинального обмена ресурсами. Распространенной фразой раньше была следующая
«Если вы импортируете jquery на сайт A из CDN и импортируете jquery на сайт B из того же CDN, вы не несете затрат, а браузер использует кэшированную версию».
Теперь это не так.
https://twitter.com/seldo/status/1486122838801063938
Что это изменение означает для вас? Если ваши сайты находятся на современном хостинге, который предоставляет CDN и поддерживает HTTP/2, вам следует отказаться от услуг третьих сторон и отправлять все ресурсы самостоятельно. Полагаясь на ресурсы третьих сторон, вы не получите никакой пользы в 2020 году.
Хотя последний пункт может быть спорным, особенно для тех, кто размещает свои собственные ресурсы на маршрутизаторах, не поддерживающих HTTP/2 (смотрим на вас, Heroku), важно отметить, что доставка зависимостей от первой стороны предлагает гораздо больше контроля и может привести к лучшей производительности, поскольку не нужно делать дополнительный HTTP-шарп к третьей стороне.
Вот и все, друзья!
Хотя HTTP/2 предлагает тонну преимуществ в производительности, особенно в отношении загрузки активов. Это не означает, что мы не можем найти преимущества в объединении наших приложений. В конце концов, если вы оценили все варианты и сделали то, что лучше всего подходит для вашего проекта, вот что действительно важно.