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

Alex Alex 04 грудня 2019

Продумані запити: стратегії кешування в століття PWA
Давним-давно ми, у справі кешування, цілком покладалися на браузери. Розробники в ті дні майже ніяк не могли на це вплинути. Але потім з'явилися прогресивні веб-додатки (Progressive Web App, PWA), сервіс-воркеры, API Cache. Раптово сталося так, що в руках програміста виявилися широкі повноваження, владу над тим, що потрапляє в кеш, і над тим, як воно туди потрапляє. Тепер ми можемо кешувати все, що хочемо... в цьому-то і криється потенційна проблема.



Медіа-файли, в особливості — зображення, це те, що в наші дні є основною складовою частиною розмір типовою веб-сторінки. З часом ситуація лише погіршується. Для того щоб підвищити продуктивність сторінок, ми намагаємося кешувати якомога великі обсяги подібних даних. Але чи варто це робити? В більшості випадків — не варто. Навіть враховуючи те, що в нашому розпорядженні тепер є всі ці новомодні технології, нам, для того, щоб досягти високої продуктивності веб-сторінок, все ще необхідно дотримуватися одного простого правила. Воно полягає в тому, що запитувати з сервера потрібно лише те, що потрібно, прагнучи при цьому до того, щоб у відповіді на кожен запит приходило б якомога менше даних.

Ми прагнемо до того, щоб наші проекти викликали б у відвідувачів лише позитивні відчуття. При цьому нам не хотілося б перевантажувати мережеві з'єднання і жорсткі диски користувачів. Це значить, що настав час дати хід деяким класичним практичним прийомам, поекспериментувати зі стратегіями кешування медіаданих і вивчити хитрощі 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 підтримує три значення (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
);
);
}
});

Коли мережа доступна — все працює так, як очікується:


Коли мережа доступна — аватарки виводяться так, як очікується

Але якщо мережеве з'єднання переривається, зображення буде автоматично замінено на запасну картинку. При цьому сторінка все ще виглядає більш або менш прийнятно.


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

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

▍Поважайте бажання користувача, що стосується економії трафіку


Деякі користувачі, прагнучи знизити споживання трафіку, використовують «легкий» режим браузера або включають настройку яка може називатися «Економія даних» або «Економія трафіку». Коли це відбувається, браузер часто відправляє в запитах заголовок 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. Після того, як це буде зроблено, при перериванні запиту на завантаження зображень можна буде побачити щось подібне до наступного малюнка.


Аватар і зображення, що завантажуються в звичайних умовах ресурсу 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: `$static`
},
images: {
name: `$images`,
limit: 75
},
pages: {
name: `$pages`,
limit: 5
},
other: {
name: `$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-гігабітного оптоволокну.

Шановні читачі! Користуєтеся ви сучасними можливостями кешування медіа-ресурсів у своїх веб-проектах?


Source: habr.com

Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.