Продуманные запросы: стратегии кэширования в век PWA

в 9:30, , рубрики: javascript, pwa, Блог компании RUVDS.com, Клиентская оптимизация, кэширование, разработка, Разработка веб-сайтов

Давным-давно мы, в деле кэширования, всецело полагались на браузеры. Разработчики в те дни почти никак не могли на это повлиять. Но потом появились прогрессивные веб-приложения (Progressive Web App, PWA), сервис-воркеры, API Cache. Внезапно случилось так, что в руках программиста оказались широкие полномочия, власть над тем, что попадает в кэш, и над тем, как оно туда попадает. Теперь мы можем кэшировать всё, что хотим… в этом-то и кроется потенциальная проблема.

Продуманные запросы: стратегии кэширования в век PWA - 1

Медиа-файлы, в особенности — изображения, это то, что в наши дни является основной составной частью размера типичной веб-страницы. Со временем ситуация лишь ухудшается. Для того чтобы повысить производительность страниц, мы пытаемся кэшировать как можно большие объёмы подобных данных. Но стоит ли это делать? В большинстве случаев — не стоит. Даже учитывая то, что в нашем распоряжении теперь есть все эти новомодные технологии, нам, для того, чтобы достичь высокой производительности веб-страниц, всё ещё необходимо придерживаться одного простого правила. Оно заключается в том, что запрашивать с сервера нужно лишь то, что нужно, стремясь при этом к тому, чтобы в ответе на каждый запрос приходило бы как можно меньше данных.

Мы стремимся к тому, чтобы наши проекты вызывали бы у посетителей лишь положительные ощущения. При этом нам не хотелось бы перегружать сетевые соединения и жёсткие диски пользователей. Это значит, что пришло время дать ход некоторым классическим практическим приёмам, поэкспериментировать со стратегиями кэширования медиаданных и изучить хитрости API Cache, которые скрыты в рукаве у сервис-воркеров.

Благие намерения

Всё то, что мы узнали уже очень давно, оптимизируя веб-страницы в расчёте на медленные модемные соединения, стало крайне актуальным в наши дни, с развитием мобильного доступа к вебу. Всё это не теряет ценности и в работе над современными проектами, рассчитанными на глобальную аудиторию. Ненадёжные сети, или сети с большими задержками — это, во многих частях планеты, всё ещё норма. Это должно помочь нам не забывать о том, что нельзя с уверенностью полагаться на предположения об уровне качества сетей, сделанные на основе наблюдения за теми местами, где внедрены передовые технологии. И это касается рекомендованных подходов к оптимизации производительности: история подтвердила то, что подходы, помогающие улучшать производительность сегодня, не потеряют своей ценности и в будущем.

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

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

Запрашивайте с сервера только то, что нужно

Как я уже говорил, современный веб переполнен медиа-данными. Изображения и видеофайлы стали ведущим средством общения. Они, если речь идёт о коммерческих сайтах, могут повышать продажи, но если говорить о скорости загрузки материалов и о скорости вывода страниц, то чем их больше — тем хуже. Учитывая это, мы должны стремиться к тому, чтобы каждое изображение (и каждое видео, и прочее подобное), прежде чем заслужить место на странице, могло бы доказать свою необходимость в этом месте.

Несколько лет назад мой кулинарный рецепт был включён в газетную статью о готовке. Я не подписан на печатную версию той газеты, поэтому, когда статья вышла, я пошёл на сайт для того, чтобы взглянуть на статью. Создатели сайта недавно провели его редизайн. Они тогда решили загружать все статьи в модальное окно, раскрывавшееся почти на весь экран и располагавшееся поверх их главной страницы. Это означало, что для загрузки статьи нужно было загрузить всё то, что нужно для вывода страницы статьи, плюс — всё то, что нужно для формирования домашней страницы ресурса. А на домашней странице была видеореклама. И не одна. И она, конечно, запускалась автоматически.

Я, когда зашёл на тот сайт, открыл инструменты разработчика и выяснил, что размер страницы превышает 15 Мб. Тогда был запущен проект What Does My Site Cost?. Он позволяет узнать о том, сколько стоит использование сайта в мобильных сетях разных стран. Я решил проверить сайт газеты с помощью этого проекта. Оказалось, что реальная стоимость просмотра этого сайта для среднего пользователя из США превышает стоимость одного выпуска бумажной версии газеты. Одним словом — бардак.

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

Изображения и видео могут прийтись очень кстати для привлечения посетителей к неким материалам в условиях высокого уровня соперничества разных материалов друг с другом (на домашней страницы газеты, например). Но если нужно, чтобы посетитель спокойно и сосредоточенно занимался бы чем-то одним (скажем — читал бы выбранную статью), ценность медиа-материалов может упасть с уровня «очень важно» до уровня «не помешает». Да, исследования показали, что изображения очень хороши в деле привлечения внимания, но после того, как посетитель попал на некую страницу, они уже оказываются далеко не такими важными. Картинки лишь удлиняют время загрузки страниц и удорожают доступ к сайту. А когда на страницы добавляют всё больше и больше медиаматериалов, ситуация лишь ухудшается.

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

Запрашивайте с сервера самые маленькие файлы из тех, что вам подходят

Предположим, что мы подобрали изображение, которое в статье совершенно необходимо. Теперь надо задаться одним очень важным вопросом: «Как быстрее всего доставить это изображение пользователю?». Ответ на этот вопрос может быть как очень простым, так и очень сложным. Простой ответ — это выбор самого подходящего графического формата (и тщательная оптимизация того, что мы в итоге отправим пользователю). Сложный ответ — это полное воссоздание изображения в новом формате (например, если смена растрового формата на векторный оказывается самым эффективным решением).

▍Предлагайте браузерам альтернативные форматы файлов

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

Сделать это можно, использовав в теге picture или video несколько элементов source. Подобная работа начинается с создания различных вариантов медиа-ресурса. Например, одно и то же изображение сохраняют в форматах WebP и JPG. Весьма вероятно то, что WebP-изображение будет иметь меньший размер, чем JPG-изображение (правда, наверняка это не скажешь, такие вещи стоит проверять самостоятельно). Подготовив альтернативные варианты ресурса, пути к ним можно поместить в элемент picture:

<picture>
  <source srcset="my.webp" type="image/webp">
  <img align="center" src="my.jpg" alt="Descriptive text about the picture.">
</picture>

Браузеры, которые распознают элемент picture, проверят элемент source прежде чем принимать решение о том, какое именно изображение запросить. Если браузер поддерживает MIME-тип "image/webp", будет выполнен запрос на получение WebP-изображения. Если нет (или если браузер не знает об элементах picture) — будет запрошено обычное JPG-изображение.

Этот подход хорош тем, что он позволяет отдавать пользователям оптимизированные изображения сравнительно небольшого размера и при этом не прибегать к JavaScript.

То же самое можно делать и с видеофайлами:

<video controls>
  <source src="my.webm" type="video/webm">
  <source src="my.mp4" type="video/mp4">
  <p>Your browser doesn’t support native video playback,
    but you can <a href="my.mp4" download>download</a>
    this video instead.</p>
</video>

Браузеры, которые поддерживают формат WebM, загрузят то, что находится в первом элементе source. Браузеры, которые WebM не поддерживают, но понимают формат MP4, запросят видео из второго такого элемента. А браузеры, которые не поддерживают тег video, просто покажут строчку текста, сообщающую пользователю о том, что он может загрузить соответствующий файл.

То, в каком порядке идут элементы source, имеет значение. Браузеры выбирают первый из таких элементов, которым могут воспользоваться. Поэтому если поместить оптимизированный альтернативный вариант ссылки на изображение после варианта, отличающегося более высоким уровнем совместимости, это может закончится тем, что ни один браузер никогда этот оптимизированный вариант не загрузит.

В зависимости от особенностей вашего проекта этот подход, основанный на разметке, может вас не устроить, и вы можете решить, что вам лучше подходит обработка подобных вещей на сервере. Например, если запрашивается JPG-файл, но браузер при этом поддерживает формат WebP (что указано в заголовке Accept), ничто вам не мешает отдать в ответ на этот запрос WebP-версию изображения. На самом деле, некоторые CDN-сервисы, например — Cloudinary, поддерживают подобные возможности, что называется, «из коробки».

▍Предлагайте браузерам изображения разных размеров

Помимо использования различных графических форматов для хранения изображений, разработчик может предусмотреть использование изображений различных размеров, оптимизированных в расчёте на размеры окна браузера. В конце концов, нет смысла загружать изображение, высота или ширина которого в 3-4 раза больше чем видимое пользователю окно браузера, выводящее это изображение. Это — пустая трата полосы пропускания. И вот здесь нам пригодятся отзывчивые изображения.

Рассмотрим пример:

<img align="center" src="medium.jpg"
  srcset="small.jpg 256w,
    medium.jpg 512w,
    large.jpg 1024w"
  sizes="(min-width: 30em) 30em, 100vw"
  alt="Descriptive text about the picture.">

В этом «заряженном» элементе img происходит много всего интересного. Разберём некоторые подробности о нём:

  • Этот элемент img предлагает браузеру три варианта размера JPG-файла: 256 пикселей в ширину (small.jpg), 512 пикселей в ширину (medium.jpg) и 1024 пикселя в ширину (large.jpg). Сведения об именах файлов находятся в атрибуте srcset. Они снабжены дескрипторами ширины.
  • Атрибут src содержит имя файла, используемого по умолчанию. Этот атрибут играет роль запасного варианта для браузеров, не поддерживающих srcset. Выбор изображения, используемого по умолчанию, вероятно, будет зависеть от особенностей страницы и от того, в каких условиях её обычно просматривают. Я порекомендовал бы указывать здесь, в большинстве случаев, имя самого маленького изображения, но если основной объём трафика подобной страницы приходится на старые настольные браузеры, то тут, возможно, стоит использовать изображение среднего размера.
  • Атрибут sizes — это презентационная подсказка, которая сообщает браузеру о том, как изображение будет выводиться в различных сценариях использования (то есть — внешний размер изображения) после применения CSS. В этом примере указано, что изображение будет занимать всю ширину области просмотра (100vw) до тех пор, пока она не достигнет 30 em в ширину (min-width: 30em), после чего ширина изображения будет равняться 30 em. Значение sizes может быть очень простым, может быть оно и очень сложным — всё зависит от нужд проекта. Если его не задавать — это приведёт к использованию его стандартного значения, равного 100vw.

Можно даже скомбинировать этот подход с выбором различных форматов изображений и разных вариантов их обрезки в одном элементе picture.

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

Откладывайте выполнение запросов (если это возможно)

Когда-то давно в Internet Explorer появилась поддержка нового атрибута, который позволял разработчикам деприоритизировать конкретные элементы img ради ускорения вывода страницы. Речь идёт об атрибуте lazyload. Этот атрибут не стал общепринятым стандартом, но он представлял собой достойную попытку организовать, без применения JavaScript, задержку загрузки изображений до тех пор, пока они не окажутся в видимой области страницы (или близко к такой области).

С тех пор появилось бесчисленное множество JavaScript-реализаций систем ленивой загрузки изображений, но недавно компания Google предприняла попытку реализовать это с использованием более декларативного подхода, представив атрибут loading.

Атрибут loading поддерживает три значения (auto, lazy и eager), определяющие то, как нужно обходиться с соответствующим ресурсом. Для нас интереснее всего выглядит значение lazy, так как оно позволяет отложить загрузку ресурса до того момента, как он достигнет определённого расстояния от области просмотра.

Добавим этот атрибут к тому, что у нас уже есть:

<img align="center" src="medium.jpg"
  srcset="small.jpg 256w,
    medium.jpg 512w,
    large.jpg 1024w"
  sizes="(min-width: 30em) 30em, 100vw"
  loading="lazy"
  alt="Descriptive text about the picture.">

Использование этого атрибута способствует некоторому росту производительности страниц в браузерах, основанных на Chromium. Хочется надеяться, что он войдёт в веб-стандарты, и что он появится в других браузерах. Но, пока этого не произошло, вреда от его использования не будет, так как браузеры, которые не понимают некий атрибут, просто его игнорируют.

Этот подход отлично дополняет стратегия приоритизации загрузки медиаданных, но прежде чем мы об этом поговорим, предлагаю поближе взглянуть на сервис-воркеры.

Управление запросами в сервис-воркерах.

Сервис-воркеры — это особый тип веб-воркеров. Они используют API Fetch и обладают возможностью перехватывать и модифицировать все сетевые запросы, а также отвечать на запросы. У них, кроме того, есть доступ к API Cache и к другим асинхронным клиентским хранилищам данных, к таким, как IndexedDB. IndexedDB может использоваться, например, в роли хранилища ресурсов.

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

▍На всякий случай храните в кэше резервные изображения

Исходя из предположения о том, что некие резервные варианты изображений, применяемые в особых случаях, используются в различных сценариях работы с сетью, можно создать именованную функцию, которая будет возвращать соответствующий ресурс:

function respondWithFallbackImage() {
  return caches.match( "/i/fallbacks/offline.svg" );
}

Потом, в обработчике события fetch, можно использовать эту функцию для выдачи запасного изображения в случае неработоспособности запросов на получение обычных изображений:

self.addEventListener( "fetch", event => {
  const request = event.request;
  if ( request.headers.get("Accept").includes("image") ) {
    event.respondWith(
      return fetch( request, { mode: 'no-cors' } )
        .then( response => {
          return response;
        })
        .catch(
          respondWithFallbackImage
        );
    );
  }
});

Когда сеть доступна — всё работает так, как ожидается:

Продуманные запросы: стратегии кэширования в век PWA - 2

Когда сеть доступна — аватарки выводятся так, как ожидается

Но если сетевое соединение прерывается, изображения будет автоматически заменены на запасную картинку. При этом страница всё ещё выглядит более или менее приемлемо.

Продуманные запросы: стратегии кэширования в век PWA - 3

Универсальное запасное изображение, выводимое вместо аватарок в том случае, если сеть недоступна

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

▍Уважайте желание пользователя, касающееся экономии трафика

Некоторые пользователи, стремясь снизить потребление трафика, используют «лёгкий» режим браузера или включают настройку которая может называться «Экономия данных» или «Экономия трафика». Когда это происходит, браузер часто отправляет в запросах заголовок Save-Data.

В сервис-воркере можно проверять запросы на предмет наличия этого заголовка и соответствующим образом настраивать ответы на запросы. Итак, сначала проверяем заголовок:

let save_data = false;
if ( 'connection' in navigator ) {
  save_data = navigator.connection.saveData;
}

Затем, в обработчике fetch, ответственном за работу с изображениями, можно принять решение о том, что в сеть соответствующий запрос отправлять не нужно. Вместо этого на него можно ответить, передав браузеру запасное изображение из кэша:

self.addEventListener( "fetch", event => {
  const request = event.request;
  if ( request.headers.get("Accept").includes("image") ) {
    event.respondWith(
      if ( save_data ) {
        return respondWithFallbackImage();
      }
      // код, который мы уже рассматривали
    );
  }
});

Тут можно пойти и ещё дальше, настроив функцию respondWithFallbackImage() так, чтобы она выдавала бы разные изображения в зависимости от того, каким именно был исходный запрос. Для того чтобы это сделать, можно определить на глобальном уровне сервис-воркера несколько запасных изображений:

const fallback_avatar = "/i/fallbacks/avatar.svg",
      fallback_image = "/i/fallbacks/image.svg";

Оба эти файла потом, при обработке события установки сервис-воркера, надо кэшировать:

return cache.addAll( [
  fallback_avatar,
  fallback_image
]);

И наконец, в respondWithFallbackImage() можно выдать подходящее изображение, проанализировав URL, используемый для загрузки ресурса. На моём сайте, например, аватары загружаются из webmention.io. В результате в функции выполняется следующая проверка:

function respondWithFallbackImage( url ) {
  const image = avatars.test( /webmention.io/ ) ? fallback_avatar
                                                 : fallback_image;
  return caches.match( image );
}

Когда в код внесено это изменение, понадобится обновить обработчик fetch так, чтобы при вызове функции respondWithFallbackImage() ей передавался бы аргумент request.url. После того, как это будет сделано, при прерывании запроса на загрузку изображений можно будет увидеть нечто подобное следующему рисунку.

Продуманные запросы: стратегии кэширования в век PWA - 4

Аватар и изображение, загружаемые в обычных условиях с ресурса webmention, при наличии у запроса заголовка Save-Data заменены двумя разными изображениями

Далее — нам нужно выработать некие общие правила, касающиеся работы с медиа-ресурсами. Применение этих правил, конечно, зависит от ситуации.

▍Стратегия кэширования: приоритизация определённых медиа-ресурсов

Опыт подсказывает мне, что медиа-ресурсы в вебе, особенно — изображения, имеют тенденцию подпадать под одну из трёх категорий. Эти категории отличаются тем, что входящие в них изображения имеют разную ценность в обеспечении правильной работы проекта. На одном конце спектра находятся элементы, которые не повышают ценности проекта. На другом конце — критически важные ресурсы, которые являются безусловно ценными. Например — это графики, без которых нельзя будет понять некий текст. Где-то посередине находятся ресурсы, которые, так сказать, «не помешают». Они повышают ценность проекта, но их нельзя назвать жизненно важными для понимания его содержимого.

Если вы рассмотрите свои графические ресурсы, учитывая эту классификацию, то вы сможете сформировать некое общее руководство по работе с каждым из этих видов ресурсов в разных ситуациях. Другими словами — вот пример стратегии кэширования.

Стратегия загрузки медиа-ресурсов, разбитых по классам, отражающим их важность для понимания содержимого проекта

1

  • Категория ресурса: «критический».
  • Быстрое соединение, наличие заголовка Save-Data, медленное соединение: загрузка ресурса.
  • Отсутствие соединения: замена местозаполнителем.

2

  • Категория ресурса: «не помешает».
  • Быстрое соединение: загрузка ресурса.
  • Наличие заголовка Save-Data, медленное соединение, отсутствие соединения: замена местозаполнителем.

3

  • Категория ресурса: «неважный»
  • Быстрое соединение, наличие заголовка Save-Data, медленное соединение, отсутствие соединения: полностью исключаются из состава содержимого страницы.

▍О разделении ресурсов на категории

Когда дело доходит до чёткого разделения «критических» ресурсов и ресурсов категории «не помешает», полезно организовать их хранение в разных папках (или сделать что-то подобное). При таком подходе в сервис-воркер можно добавить некую логику, которая поможет ему разобраться с тем, что есть что. Например я, на моём персональном сайте, либо храню критически важные изображения у себя, либо беру их с сайта моей книги. Зная об этом, я могу, для отбора этих изображений, пользоваться регулярными выражениями, анализирующими домен, к которому направлен запрос:

const high_priority = [
    /aaron-gustafson.com/,
    /adaptivewebdesign.info/
  ];

Имея переменную high_priority, я могу создать функцию, которая способна узнать, например, является ли некий запрос на загрузку изображения высокоприоритетным:

function isHighPriority( url ) {
  // с каким количеством высокоприоритетных ссылок мы имеем дело?
  let i = high_priority.length;
  // пройдёмся по ним в цикле
  while ( i-- ) {
    // соответствует ли запрошенный URL этому регулярному выражению?
    if ( high_priority[i].test( url ) ) {
      // да, это высокоприоритетный запрос
      return true;
    }
  }
  // нет совпадений, нет высокоприоритетных запросов
  return false;
}

Оснащение проекта поддержкой приоритизации запросов на загрузку медиа-файлов требует лишь добавления новой проверки условия в обработчик события fetch. Это реализуется по той же схеме, которая использовалась для работы с заголовком Save-Data. То, как именно это будет реализовано у вас, весьма вероятно, будет отличаться от того, что получилось у меня, но я всё же хочу поделиться с вами своим вариантом решения этой задачи:

// Сначала проверим кэш
  // Если в кэше есть нужное изображение - вернём его
  // Если в кэше нет изображения - продолжим

// Является ли изображение высокоприоритетным?
if ( isHighPriority( url ) ) {

  // Загрузить изображение
    // Если загрузка оказалась успешной - сохранить копию изображения в кэше
    // Если нет - вернуть  "оффлайновый" местозаполнитель

// Изображение не является высокоприоритетным
} else {

  // Надо ли экономить трафик?
  if ( save_data ) {

    // Вернуть местозаполнитель "экономия трафика"

  // Трафик экономить не надо
  } else {

    // Загрузить изображение
      // Если загрузка оказалась успешной - сохранить копию изображения в кэше
      // Если нет - вернуть  "оффлайновый" местозаполнитель
  }
}

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

Держите кэш в чистоте

То, что разработчик может управлять тем, что именно кэшируется и сохраняется в системе пользователя, даёт ему огромные возможности. Но на разработчика ложится и серьёзная ответственность. Он должен постараться правильно и без вреда для пользователя реализовать эти возможности.

Все стратегии кэширования, вероятно, будут друг от друга отличаться. По крайней мере — в мелочах. Если мы, например, публикуем на сайте книгу, то вполне может иметь смысл помещение в кэш всех её материалов. Это поможет пользователю читать книгу без подключения к сети. Книга имеет некий фиксированный размер, и, исходя из предположения о том, что в её состав не входят гигабайты изображений и видеозаписей, пользователю будет только лучше от того, что ему не придётся загружать каждую главу по-отдельности.

Однако если кэшировать каждую статью и каждую фотографию на новостном сайте — это очень скоро приведёт к переполнению жёстких дисков пользователей такого сайта. Если на сайте имеется неизвестное заранее число страниц и других ресурсов, то очень важно иметь стратегию кэширования, которая предусматривает задание жёстких лимитов на количество кэшируемых ресурсов.

Один из подходов к введению подобных ограничений заключается в создании нескольких различных блоков, связанных с кэшированием различного содержимого. Чем быстрее устаревают те или иные ресурсы — тем жёстче должны быть ограничения на количество элементов, которое хранится в кэше для таких ресурсов. Конечно, как бы там ни было, кэш ограничен аппаратными возможностями компьютера, но тут перед нами встаёт один важный вопрос: «Неужели мы стремимся к тому, чтобы кэш наших сайтов занимал бы по 2 Гб дисков пользователей?».

Вот пример, опять же, взятый с моего сайта:

const sw_caches = {
  static: {
    name: `${version}static`
  },
  images: {
    name: `${version}images`,
    limit: 75
  },
  pages: {
    name: `${version}pages`,
    limit: 5
  },
  other: {
    name: `${version}other`,
    limit: 50
  }
}

Здесь я определил несколько кэшей. У каждого из них есть имя, свойство name, используемое для обращения к нему в API Cache. У имён кэшей есть префиксы версии, устанавливаемые с помощью переменной version. Значение этой переменной задаётся в коде сервис-воркера. Наличие версии позволяет, при необходимости, очищать все кэши.

За исключением кэша static, который используется для кэширования статических ресурсов, у всех кэшей есть свойство limit, используемое для ограничения количества ресурсов, хранимых в кэше. Так, например, кэшируются лишь 5 самых свежих посещённых пользователем страниц. Количество кэшируемых изображений ограничено семьюдесятью пятью, и так далее. Именно такой подход описан в этой замечательной книге Джереми Кейта об оффлайновом режиме работы веб-проектов (если вы её ещё не читали — рекомендую её почитать; вот её первая глава).

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

function trimCache(cacheName, maxItems) {
  // Открыть кэш
  caches.open(cacheName)
  .then( cache => {
    // Получить ключи и посчитать их
    cache.keys()
    .then(keys => {
      // Хранится ли в кэше больше элементов, чем должно храниться?
      if (keys.length > maxItems) {
        // Удалить самый старый элемент и снова запустить эту же функцию
        cache.delete(keys[0])
        .then( () => {
          trimCache(cacheName, maxItems)
        });
      }
    });
  });
}

Вызывать этот код можно при каждой загрузке новой страницы. Если он выполняется в сервис-воркере, то работать он будет в отдельном потоке и не ухудшит отзывчивость сайта. Вызывают этот код, пользуясь механизмом отправки сообщений (с помощью postMessage()) сервис-воркеру из главного потока выполнения JavaScript:

// Сначала проверим наличие активного сервис-воркера
if ( navigator.serviceWorker.controller ) {
  // Потом добавим прослушиватель событий
  window.addEventListener( "load", function(){
    // Сообщим сервис-воркеру о том, что ему необходимо выполнить очистку кэша
    navigator.serviceWorker.controller.postMessage( "clean up" );
  });
}

Последний шаг нашей работы заключается в оснащении сервис-воркера функционалом по приёму сообщений:

addEventListener("message", messageEvent => {
  if (messageEvent.data == "clean up") {
    // проходимся по кэшам
    for ( let key in sw_caches ) {
      // если кэш имеет лимит
      if ( sw_caches[key].limit !== undefined ) {
        // уменьшить его до размеров этого лимита
        trimCache( sw_caches[key].name, sw_caches[key].limit );
      }
    }
  }
});

Здесь сервис-воркер ожидает входящих сообщений и реагирует на сообщение clean up, запуская функцию trimCache() для каждого из кэшей, у которого задано свойство limit.

Этот подход едва ли можно назвать очень уж изящным, но своё дело он делает. Куда лучше было бы принимать решение об очистке кэша, основываясь на том, как часто используются его элементы, или на том, сколько места они занимают на диске. (Удаление элементов кэша, основываясь лишь на том, когда они были кэшированы, далеко не самая удачная стратегия.) Но, к сожалению, при исследовании кэша мы пока не можем получать столь подробные сведения о его элементах. И я, на самом деле, прямо сейчас работаю над решением этих проблем в API Cache.

Итоги: пользователи — это всегда самое важное

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

Разделяйте медиа-файлы на критически важные, на такие, которые «не помешают», и на те, без которых можно обойтись. Убирайте всё лишнее и как следует оптимизируйте то, что осталось. Готовьте разные варианты изображений, используйте разные форматы и размеры. Давайте приоритет самым маленьким версиям картинок для того, чтобы облегчить жизнь тех пользователей, которые пользуются медленными сетями с высокими задержками. Если пользователь выражает желание экономить трафик — уважайте это желание и заранее подготовьтесь к работе в таком режиме. С умом подходите к кэшированию, особенно внимательно относясь к вопросам очистки кэшей и экономии дискового пространства компьютеров пользователей. И, наконец, регулярно анализируйте используемую вами стратегию кэширования — особенно в тех её частях, которые относятся к большим медиа-файлам.

Следуйте этим рекомендациям и каждый из ваших пользователей скажет вам «спасибо». От тех из них, которые подключаются к интернету через сельскую мобильную сеть в Индии и смотрят страницы на JioPhone, до тех, которые живут в Кремниевой долине и сидят в Сети с мощных игровых ноутбуков, подключённых к 10-гигабитному оптоволокну.

Уважаемые читатели! Пользуетесь ли вы современными возможностями кэширования медиа-ресурсов в своих веб-проектах?

Продуманные запросы: стратегии кэширования в век PWA - 5


Продуманные запросы: стратегии кэширования в век PWA - 6

Автор: ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js