[Перевод] Каскадная инвалидация кэша. Часть 2
В первой части перевода материала, посвящённого каскадной инвалидации кэша, мы обсудили сущность проблемы и рассмотрели один из вариантов её решения, который заключается в использовании карт импорта. Его плюс — лёгкость реализации. А минус — слабая поддержка браузерами. Сегодня поговорим о других способах решения этой проблемы.
Подход №2: сервис-воркеры
Второй вариант решения проблемы заключается в воспроизведении функционала карт импорта средствами сервис-воркера.
Например, используя сервис-воркер можно прослушивать события fetch
, которые направлены на загрузку материалов, находящихся по адресам, соответствующих ключам карты импорта. Выполняя эти запросы, можно загружать файлы, в имена которых входят хэши их содержимого:
const importMap = {
'/main.mjs': '/main-1a2b.mjs',
'/dep1.mjs': '/dep1-b2c3.mjs',
'/dep2.mjs': '/dep2-3c4d.mjs',
'/dep3.mjs': '/dep3-d4e5.mjs',
'/vendor.mjs': '/vendor-5e6f.mjs',
};
addEventListener('fetch', (event) => {
const oldPath = new URL(event.request.url, location).pathname;
if (importMap.hasOwnProperty(oldPath)) {
const newPath = importMap[oldPath];
event.respondWith(fetch(new Request(newPath, event.request)));
}
});
Правда, учитывая то, что выше приведён код сервис-воркера, надо понимать, этот код этот заработает только после того, как сервис-воркер будет установлен и активирован. А это означает, что при первой загрузке сайта запрошены будут файлы, в именах которых нет хэшей. Файлы же с хэшами в именах будут запрашиваться при последующих загрузках сайта. Другими словами, тут мы имеем дело с двойной загрузкой каждого файла.
Если это учесть, то может показаться, что сервис воркер не является подходящим решением проблемы каскадной инвалидации кэша.
Однако тут я попрошу вас позволить мне ненадолго подвергнуть критической оценке давно существующие подходы к кэшированию. Давайте подумаем о том, что произойдёт, если перестать использовать хэши содержимого в именах файлов, вместо этого поместив сведения о хэшах в код сервис-воркера.
Именно так работают инструменты, наподобие Workbox, выполняющие предварительное кэширование ресурсов. Они генерируют хэши содержимого каждого файла из сборки и хранят соответствия имён файлов в сервис-воркере (получается нечто вроде внешней карты импорта). Они, кроме того, кэшируют ресурсы при первой установке сервис-воркера и добавляют прослушиватели события fetch
, которые отдают кэшированные файлы в ответ на запросы, адреса которых соответствуют тем, что есть в карте импорта.
Хотя идея, в соответствии с которой клиенту поступают файлы, в именах которых нет сведений о версиях их содержимого, может показаться пугающей (и противоречащей всему, чему вас учили), запрос на загрузку соответствующего ресурса выполняется только тогда, когда устанавливается сервис-воркер. Дальнейшие запросы на загрузку такого ресурса проходят через API Cache Storage (которое не использует заголовки кэширования), и новые запросы к серверу выполняются только тогда, когда выполняется развёртывание новой версии сервис-воркера (а свежая версия этих файлов понадобится вам в любом случае).
В результате, до тех пор, пока вы не начнёте разворачивать новые версии модулей, не обновляя при этом и сервис воркер (а это, определённо, делать не рекомендуется), вы никогда не столкнётесь с конфликтом или несовпадением версий.
Для организации предварительного кэширования файлов с использованием библиотеки workbox-precaching можно передать методу этой библиотеки precacheAndRoute()
адреса файлов и строки со сведениями о версиях этих файлов:
import {preacacheAndRoute} from 'workbox-precaching';
precacheAndRoute([
{url: '/main.mjs', revision: '1a2b'},
{url: '/dep1.mjs', revision: 'b2c3'},
{url: '/dep2.mjs', revision: '3c4d'},
{url: '/dep3.mjs', revision: 'd4e5'},
{url: '/vendor.mjs', revision: '5e6f'},
]);
Как именно генерировать строки с версиями — решает сам разработчик. Но если ему не хочется создавать их самостоятельно, задачу генерирования манифеста предварительного кэширования помогут упростить пакеты workbox-build, workbox-cli и workbox-webpack-plugin (они могут даже сгенерировать весь код сервис-воркера).
В моём демонстрационном проекте имеется пример реализации предварительного кэширования средствами сервис-воркера в Rollup-приложении (с помощью workbox-cli
) и в webpack-приложении (с помощью workbox-webpack-plugin
).
Подход №3: собственные скрипты для загрузки ресурсов
Если на вашем сайте нет возможности использовать ни карты импорта, ни сервис-воркеры, то вот — третий подход к решению проблемы. Он заключается в реализации функционала карт импорта с помощью собственного скрипта для загрузки ресурсов.
Если вам знакомы загрузчики модулей в стиле AMD (вроде SystemJS или RequireJS), то вы, возможно, знаете и о том, что эти загрузчики модулей обычно поддерживают указание псевдонимов модулей. На самом деле, SystemJS поддерживает указание псевдонимов с использованием синтаксиса карт импорта. В результате нашу проблему легко решить так, что это решение окажется ориентированным на будущее (и, кроме того, заработает во всех существующих браузерах).
Если вы используете Rollup, то вы можете установить опцию output.format в значение system
. В этом случае создание карты импорта для приложения будет выполняться точно так же, как было рассмотрено в описании первого подхода к решению проблемы каскадной инвалидации кэша.
В моём демонстрационном приложении есть пример сайта, в котором Rollup используется для сборки материалов в формате, подходящем для SystemJS, и для создания карты импорта, с помощью которой загружаются хэшированные версии файлов.
▍Webpack и загрузка ресурсов с помощью скриптов
Webpack тоже способен помочь в деле загрузки ресурсов с помощью собственного скрипта, но загрузчик, генерируемый webpack, в отличие от классических AMD-загрузчиков, уникален для каждого конкретного бандла.
Преимущество этого подхода заключается в том, что среда выполнения webpack может (и так она, на самом деле, и работает) включать собственные мэппинги между именами/идентификаторами фрагментов и их адресами (это похоже на то, что я тут рекомендую). Это означает, что webpack-бандлы, в которых применяется разделение кода, с меньшей долей вероятности подвержены проблеме каскадной инвалидации кэша.
Собственно говоря, хорошая новость для пользователей webpack заключается в том, что если они правильно настроили сборку проекта с помощью webpack (разделив код на фрагменты, так, как описано в руководстве webpack по кэшированию), тогда изменение в коде отдельного модуля не должно приводить к инвалидации более чем двух фрагментов (один — тот, который содержит изменённый модуль, второй — тот, который содержит среду выполнения).
Но у меня есть и плохая новость для тех, кто применяет webpack для сборки проектов. Дело в том, что внутренняя система мэппинга этого бандлера нестандартна. Это значит то, что её нельзя интегрировать с существующими инструментами, и то, что пользователь не может её настроить. Например, нельзя самостоятельно генерировать выходные файлы (то есть, поступить так, как было описано в рассказе о первом подходе к решению проблемы) и поместить в мэппинг собственные хэши. И это — минус webpack, так как хэши, используемые этим бандлером, основаны не на содержимом выходных файлов, а на содержимом файлов с исходным кодом и на конфигурации сборки. А это может вести к небольшим и трудноуловимым ошибкам (например — вот, вот и вот — сообщения о подобных ошибках).
Если вы используете webpack для сборки приложения, которое, кроме того, использует сервис-воркер, то я порекомендовал бы применять плагин workbox-webpack-plugin и стратегию кэширования, о которой речь шла в описании второго подхода к решению проблемы. Плагин сгенерирует хэши, основанные на содержимом выходных материалов webpack, а это означает, что вам не придётся беспокоиться о вышеупомянутых ошибках. В дополнение к этому, работа с именами файлов, в составе которых нет хэшей, обычно легче, чем работа с именами, в которых хэши есть.
Другие ресурсы веб-проектов
Выше я говорил о том, как работа в JavaScript-программах с «хэшированными» именами файлов, содержащих программный код, может приводить к каскадной инвалидации кэша. Но эта проблема применима и к другим материалам веб-проектов.
Так, CSS и SVG-файлы часто ссылаются на другие ресурсы (на изображения, например), имена которых могут содержать сведения о версиях соответствующих файлов в виде хэшей. Как и в случае с JS-файлами, для решения проблемы каскадной инвалидации кэша, вызываемой изменениями имён подобных ресурсов, можно использовать карты импорта или сервис-воркеры.
Для ресурсов вроде изображений и видеофайлов это никаких сложностей не представляет. Тут применимы все существующие рекомендации.
Главное здесь — помнить о том, что всегда, когда файл A загружает файл B, и, кроме того, включает в себя сведения о версии файла B в виде хэша его содержимого, инвалидация кэша для файла B вызовет и инвалидацию кэша для файла A. При работе же с ресурсами, использование которых организовано иначе, на советы, данные в этом материале, можно просто не обращать внимания.
Итоги
Надеюсь, эта статья вдохновила вас на то, чтобы вы присмотрелись бы к своему сайту и выяснили бы — влияет ли на него проблема каскадной инвалидации кэша. Легче всего это проверить, собрав сайт, изменив одну строчку кода в файле, который импортируется множеством модулей, а потом — пересобрав сайт. Если в директории, в которой лежат результаты сборки, имена изменились более чем у одного файла, это значит, что перед вами — признак каскадной инвалидации кэша. А если это так, то вам, возможно, стоит задуматься о применении в своём проекте одного из описанных здесь подходов к решению этой проблемы.
Если же рассуждать о том, что лучше выбрать, то, честно говоря, это зависит от многого.
Когда карты импорта будут широко поддерживаться браузерами, то перед нами окажется самый простой и совершенный способ борьбы с каскадной инвалидацией кэша. Но до тех пор, пока такой поддержки нет, этот механизм на практике неприменим.
Если вы уже используете сервис-воркеры, в особенности — если применяете Workbox, тогда я посоветовал бы второй из рассмотренных здесь подходов к решению проблемы. На сайте, на котором опубликован оригинал этого материала, задача предварительного кэширования ресурсов решена именно так.
Кроме того, сервис-воркеры — это единственный вариант для тех, кто применяет в продакшне JavaScript-модули. (А учитывая то, что у 98% моих пользователей имеются браузеры, которые поддерживают и сервис-воркеры, и JS-модули, мне несложно было выбрать именно этот вариант).
Если сервис-воркеры вам не подходят, тогда я порекомендовал бы третий из рассмотренных здесь подходов, предусматривающий использование SystemJS. Этот подход лучше прочих, основанных на скриптах-загрузчиках, ориентирован на будущее. С него легко будет перейти на карты импорта в то время, когда их поддержка появится во всех браузерах.
Если говорить о производительности, то выбор направления её оптимизации зависит от каждого конкретного проекта. Перед оптимизацией производительности важно провести её замеры, а потом уже решить — есть ли проблема, и надо ли с ней бороться. Если вы выпускаете новые релизы проекта нечасто, а изменения, вносимые в проект, обычно достаточно масштабны, тогда проблема каскадной инвалидации кэша, возможно, к вам отношения не имеет.
С другой стороны, если вы часто развёртываете небольшие изменения проекта, тогда ваши возвращающиеся пользователи могут столкнуться с проблемой загрузки больших объёмов кода, который уже есть в их кэшах. Решение этой проблемы будет означать значительное увеличение производительности загрузки страниц у таких пользователей.
Уважаемые читатели! Влияет ли на ваш проект проблема каскадной инвалидации кэша? Если да — просим рассказать о том, как вы планируете её решать.