Перейти к содержанию

Лучшие практики обеспечения надежности PWA

Добро пожаловать на четвертую неделю, четвертый день серии статей 30 Days of PWA! Теперь, когда мы рассмотрели, как начать создавать PWA и какие базовые и расширенные возможности они дают, пришло время погрузиться в то, как на самом деле выглядит хороший PWA и как мы можем сами начать создавать качественные приложения. На четвертой неделе мы расскажем о лучших практиках и технологиях, которые можно использовать, чтобы довести PWA до совершенства.

Только баннер Placeholder. Заменим, когда будут готовы финальные ресурсы

Краткое примечание

Эта статья посвящена лучшим практикам обеспечения надежности PWA. Если вы ищете основы автономного поведения и кэширования в PWA, ознакомьтесь со статьей Make PWA Work Offline из первой недели.

Как выглядит надежность для PWA?

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

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

Для PWA вопросы надежности, связанные с подключением пользователя к Интернету, представляют особый интерес, поскольку более традиционные веб-приложения в значительной степени зависят от наличия соединения.

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

Ускорение работы PWA с помощью стратегий кэширования

Одним из способов ускорения работы PWA и повышения надежности является минимизация запросов к сети и использование кэшированных ответов при любой возможности. Существует множество стратегий кэширования, которыми можно воспользоваться, но сегодня мы рассмотрим только два популярных варианта: cache-first и stale-while-revalidate.

Cache-First

В статье Make PWA Work Offline из первой недели мы немного рассказали о том, как работает cache-first. Стратегия cache-first — это именно то, что звучит: при событии fetch наш сервис-воркер сначала проверит кэш на наличие ответа, и обратится к сети только в том случае, если кэш не сработает.

Мы видели такой фрагмент сервис-воркера:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
self.addEventListener('fetch', event => {
    event.respondWith((async () => {
        const cache = await caches.open(CACHE_NAME);

        // Try the cache first.
        const cachedResponse = await cache.match(event.request);
        if (cachedResponse !== undefined) {

            return cachedResponse;
        } else {
            // Nothing in cache, let's go to the network.
        }
    }
}

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

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

Cache-first — это отличная отправная точка для повышения надежности производительности, но давайте рассмотрим кое-что более гибкое.

Stale-While-Revalidate

Стратегия stale-while-revalidate опирается на cache-first и дает нам лучшее из двух миров: скорость загрузки и свежесть.

Как и cache-first, эта стратегия проверяет кэш на наличие нужного ответа и возвращает его. Однако вместо того, чтобы остановиться на этом, мы все равно передадим запрос в сеть и в фоновом режиме обновим кэш, получив ответ. Если кэш пропущен, то мы сразу отправимся в сеть.

Давайте обновим наш фрагмент сервис-воркера, чтобы отразить это:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
self.addEventListener('fetch', event => {
    event.respondWith((async () => {
        const cache = await caches.open(CACHE_NAME);

        // Try the cache first like last time.
        const cachedResponse = await cache.match(event.request);
        if (cachedResponse !== undefined) {
            // Now, we fetch a new response and cache it in the background
            fetch(event.request).then( response => {
                cache.put(event.request, response.clone());
            });
            // We don't await the above line, so we return our cachedResponse right away
            return cachedResponse;
        } else {
            // Go to the network otherwise
        }
    }
}

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

Сохранение данных в автономном режиме с помощью IndexedDB

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

API для IndexedDB может быть несколько низкоуровневым для обычного использования, поэтому мы рассмотрим простую обертку под названием LocalForage.

Использование LocalForage для управления данными

Мы можем использовать гипотетический пример, чтобы понять, как можно использовать LocalForage в приложении.

Представим, что мы работаем над простым почтовым клиентом и хотим хранить X последних писем, чтобы в случае потери соединения с PWA пользователь мог прочитать свои последние письма.

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

Сначала мы можем создать экземпляр (такой же, как база данных) localforage и назвать его:

1
2
3
4
5
6
// our DB name
const databaseName = 'EmailDB';
// create an instance with our name
var emailDatabase = localforage.createInstance({
    name: databaseName,
});

Теперь добавим в него наши данные с помощью пары ключ-значение:

1
2
3
4
// Let's get our recent emails as an array
var emails = GetRecentEmails();
// and add that to our database
emailDatabase.setItem('recent-emails', emails);

В дальнейшем мы можем получить наши письма по ключу и что-то сделать со значением:

1
2
3
4
// Asynchronously fetch our emails and then populate our inbox
emailDatabase.getItem('recent-emails').then((emails) => {
    PopulateInbox(emails);
});

И, наконец, мы можем так же легко удалить данные:

1
2
// remove based on key
emailDatabase.removeItem('recent-emails');

Очень просто, правда? Для простоты использования LocalForage ограничивает свой API лишь несколькими функциями, а более подробно о его возможностях можно узнать здесь. Это отличный вариант для начала работы с IndexedDB и для хранения данных в простых случаях.

Если вам нужно что-то более гибкое для вашего PWA, то есть еще idb, который является еще одной отличной оберткой IndexedDB, обладающей большей функциональностью. Однако она более сложна в управлении базой данных и ее транзакциями, так что будьте готовы к большим сложностям. Если вам нравится стиль idb, но хочется чего-то более простого, чем LocalForage, то есть еще idb-keyval, который поддерживается создателем idb и имеет меньший размер пакета, чем LocalForage.

Подведение итогов

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

Ресурсы

Комментарии