[Обучение и практика PWA] (3) Сделайте свое веб-приложение доступным в автономном режиме

JavaScript браузер Promise PWA
[Обучение и практика PWA] (3) Сделайте свое веб-приложение доступным в автономном режиме

Серия статей «Обучение и практика PWA» организована в видеGitbook - Руководство по изучению PWA, текстовое содержимое было синхронизировано сlearning-pwa-ebook. При перепечатке просьба указывать автора и источник.

Эта статья«Обучение и практика PWA»Третья статья цикла. Код в тексте можно найти вsw-cache ветка обучения-pwaНаходить (git cloneОбратите внимание на переход на ветку sw-cache позже).

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

1. Введение

Одной из удивительных возможностей PWA является то, что они доступны в автономном режиме.

即使在离线状态下,依然可以访问的PWA

Оффлайн — это лишь одно из его функциональных проявлений, а именно:

  • Сделать наше веб-приложение доступным без сети (в автономном режиме) и даже использовать некоторые функции вместо отображения страницы ошибки «нет сетевого подключения»;
  • Давайте в случае слабой сети можем использовать кеш для быстрого доступа к нашему приложению, чтобы улучшить работу;
  • В нормальных сетевых условиях часть запрошенной пропускной способности также может быть сохранена с помощью различных методов самоконтролируемого кэширования;
  • ...

И все это на самом деле благодаря герою PWA——Service Worker.

Итак, что такое сервис-воркеры? Вы можете просто понимать Service Worker как процесс, который работает в фоновом режиме независимо от страницы интерфейса. Поэтому он не блокирует выполнение скриптов браузера и не обращается напрямую к связанным с браузером API-интерфейсам (например, DOM, localStorage и т. д.). Кроме того, он работает даже после выхода из веб-приложения, даже после закрытия браузера. Это похоже на трудолюбивую маленькую пчелу, молча работающую за веб-приложением, обрабатывающую кэширование, отправку, уведомления и синхронизацию. Поэтому, если вы хотите изучить PWA, вам не обойтись без Service Worker.

PWA背后的英雄 —— Service Worker

В следующих нескольких статьях я представлю соответствующие принципы и техническую реализацию с точки зрения того, как использовать Service Worker для достижения кэширования ресурсов, отправки сообщений, уведомлений о сообщениях и фоновой синхронизации. Эти части будут в центре внимания технологии PWA. Важно отметить, что из-за мощных возможностей Service Workers в спецификации указано, чтоService Worker может работать только в домене HTTPS.. Однако что, если у нас не будет HTTPS при разработке? Не волнуйтесь, есть еще одно интимное место - для удобства местной застройки,Сервисные работники также могут работать в домене localhost (127.0.0.1)..

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

2. Как Service Worker доступен в автономном режиме?

В этом разделе рассказывается, как Service Worker позволяет нам получать доступ к веб-приложению даже в автономном режиме. Конечно, автономный доступ — лишь одно из проявлений этого.

Во-первых, давайте подумаем, что мы на самом деле делаем, посещая веб-сайт? В общем, мы получаем ресурсы, устанавливая соединение с сервером, а затем некоторые из полученных ресурсов также будут запрашивать новые ресурсы (такие как css, js, используемые в html и т. д.). Итак, грубо говоря, когда мы посещаем веб-сайт, мы получаем доступ к этим ресурсам.

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

如何获取所需的资源?

Недостаточно иметь локальный кеш, нам также необходимо иметь возможность эффективно использовать кеш, обновлять кеш и очищать кеш, а также применять различные персонализированные стратегии кеша. А для этого нам нужен «воркер», который может управлять кешем — это часть работы сервис-воркера. Кстати может кто помнитApplicationCacheэтот API. Первоначально он был разработан для реализации кэширования веб-ресурсов, но из-за различных недостатков, таких как негибкость, теперь он был заменен Service Worker и API кэширования.

У Service Worker есть очень важная функция: вы можете прослушивать все клиентские (веб) запросы в Service Worker, а затем проксировать через Service Worker, чтобы инициировать запросы к серверным службам. Прослушивая информацию о запросе пользователя, Service Worker может решить, следует ли использовать кеш для возврата веб-запроса.

На следующем рисунке показана разница в сетевых запросах между обычным веб-приложением и веб-приложением с добавленным Service Worker:

普通Web请求(上)与使用Service Worker代理(下)的区别

Здесь необходимо подчеркнуть, что хотя браузер, SW (Service Worker) и серверная служба кажутся расположенными рядом на рисунке, на самом деле браузер (ваше веб-приложение) и SW работают на вашем компьютере. локальная машина, поэтому ПО в этом сценарии похоже на «клиентский прокси».

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

3. Как использовать Service Worker для реализации автономных приложений, открывающихся за считанные секунды.

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

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

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

3.1. Регистрация сервисных работников

Обратите внимание, что наше приложение всегда должно быть доступно постепенно, а в средах, не поддерживающих Service Workers, его доступность должна быть гарантирована. Для этого мы можем зарегистрировать нашего Service Worker (sw.js) в index.js с помощью обнаружения функций:

// index.js
// 注册service worker,service worker脚本文件为sw.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        console.log('Service Worker 注册成功');
    });
}

Здесь мы регистрируем файл sw.js как Service Worker, будьте осторожны, чтобы не написать путь к файлу неправильно.

Стоит отметить, что различные операции Service Worker спроектированы так, чтобы быть асинхронными, чтобы избежать некоторых долгосрочных операций блокировки. Эти API вызываются в виде промисов. Таким образом, вы увидите, что обещания используются снова и снова в следующих фрагментах кода. Если вы совсем не знакомы с Promise, вы можете сначала понять основную концепцию Promise здесь:Обещания (MDN)а такжеОбещания JavaScript: введение.

3.2. Жизненный цикл сервисного работника

Когда мы регистрируем Service Worker, он проходит различные этапы жизненного цикла и запускает соответствующие события. Весь жизненный цикл включает в себя: установка --> установлено --> активация --> активировано --> избыточно. При установке (установке) Service Worker будет срабатывать событие install, при активации срабатывает событие активации.

Service Worker生命周期

В следующем примере прослушивается событие установки:

// 监听install事件
self.addEventListener('install', function (e) {
    console.log('Service Worker 状态: install');
});

self— это специальная глобальная переменная в Service Worker, похожая на нашу самую распространеннуюwindowобъект.selfСсылается на текущий Service Worker.

3.3 Кэширование статических ресурсов

В предыдущем разделе мы узнали, как добавлять прослушиватели событий для запуска соответствующих операций Service Worker в нужное время. Теперь, чтобы сделать наше веб-приложение доступным в автономном режиме, нам нужно кэшировать необходимые ресурсы. Нам нужен список ресурсов, которые будут кэшироваться в кеше при активации сервис-воркера.

// sw.js
var cacheName = 'bs-0-2-0';
var cacheFiles = [
    '/',
    './index.html',
    './index.js',
    './style.css',
    './img/book.png',
    './img/loading.svg'
];

// 监听install事件,安装完成后,进行文件缓存
self.addEventListener('install', function (e) {
    console.log('Service Worker 状态: install');
    var cacheOpenPromise = caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheFiles);
    });
    e.waitUntil(cacheOpenPromise);
});

Его можно увидеть в первомcacheFilesВ мы перечисляем все статические зависимости ресурсов. Обратите внимание, что'/', так как корневой путь также может получить доступ к нашему приложению, не забудьте также кэшировать его. Когда Service Worker устанавливается, мы передаемcaches.open()а такжеcache.addAll()способ кэширования ресурса. Здесь мы устанавливаем кеш дляcacheName, это значение станет ключом для этих кэшей.

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

Интерфейс Cache предоставляет механизм хранения кэшированных пар объектов Request/Response. Интерфейс Cache, как и воркеры, выставлен под окном. Хотя он определен в стандарте сервис-воркеров, его не обязательно использовать с сервис-воркерами. -МДН

3.4 Использование кэшированных статических ресурсов

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

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

Если вы достаточно сообразительны, чтобы помнить, мы упомянули «клиентские прокси», когда представили Service Workers в первой половине статьи — использование Service Workers, чтобы помочь нам решить, как использовать кеш.

Следующая диаграмма представляет собой простую стратегию:

有cache时的静态资源请求流程

无cache时的静态资源请求流程

  1. Браузер инициирует запрос на запрос различных статических ресурсов (html/js/css/img);
  2. Service Worker перехватывает запросы браузера и запрашивает текущий кеш;
  3. Если есть кеш, вернитесь напрямую, end;
  4. Если кеша нет, пройтиfetchМетод инициирует запрос к серверу и возвращает результат запроса в браузер.
// sw.js
self.addEventListener('fetch', function (e) {
    // 如果有cache则直接返回,否则通过fetch请求
    e.respondWith(
        caches.match(e.request).then(function (cache) {
            return cache || fetch(e.request);
        }).catch(function (err) {
            console.log(err);
            return fetch(e.request);
        })
    );
});

fetchСобытие прослушивает все запросы браузера.e.respondWith()Метод обещания принимает в качестве параметра, он позволяет обслуживающим работником, возвращая данные в браузер.caches.match(e.request)Затем вы можете проверить, есть ли у текущего запроса локальный кеш: если кеш есть, вернуть его прямо в браузерcache; в противном случае Service Worker инициируетfetch(e.request)запросить и вернуть результат запроса в браузер.

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

3.5 Обновление ресурсов статического кэша

Однако, если вы будете внимательны, вы обнаружите небольшую проблему: когда мы кешируем ресурсы, если мы не отменим регистрацию (отменить регистрацию) sw.js и не очистим кеш вручную, новые статические ресурсы не будут кешироваться.

Простой способ исправить это - изменитьcacheName. Так как браузер определяет, обновляется sw.js или нет, то по байтам, поэтому модифицируйтеcacheNameперезапустит установку и кеширование ресурсов. Кроме того, в событии активации нам нужно проверитьcacheNameЕсли он меняется, это означает, что есть новые ресурсы кеша и исходный кеш необходимо удалить.

// sw.js
// 监听activate事件,激活后通过cache的key来判断是否更新cache中的静态资源
self.addEventListener('activate', function (e) {
    console.log('Service Worker 状态: activate');
    var cachePromise = caches.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            if (key !== cacheName) {
                return caches.delete(key);
            }
        }));
    })
    e.waitUntil(cachePromise);
    return self.clients.claim();
});

3.6. «Автономный поиск» кэшированных данных API

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

离线/无网环境下普通Web App(左)与PWA(右)的差异

Что тут происходит? На самом деле, секрет этого заключается в том, что это веб-приложение также будет кэшировать копию данных, запрошенных XHR. При повторном запросе мы сначала будем использовать локальный кеш (если кеш есть), затем запросим данные с сервера, и после того, как сервер вернет данные, отображение будет заменено на основе данных. Общий процесс выглядит следующим образом:

图书查询接口的缓存与使用策略

Во-первых, давайте преобразуем код из предыдущего раздела в sw.js.fetchКэшировать данные API в событиях

// sw.js
var apiCacheName = 'api-0-1-1';
self.addEventListener('fetch', function (e) {
    // 需要缓存的xhr请求
    var cacheRequestUrls = [
        '/book?'
    ];
    console.log('现在正在请求:' + e.request.url);

    // 判断当前请求是否需要缓存
    var needCache = cacheRequestUrls.some(function (url) {
        return e.request.url.indexOf(url) > -1;
    });

    /**** 这里是对XHR数据缓存的相关操作 ****/
    if (needCache) {
        // 需要缓存
        // 使用fetch请求数据,并将请求结果clone一份缓存到cache
        // 此部分缓存后在browser中使用全局变量caches获取
        caches.open(apiCacheName).then(function (cache) {
            return fetch(e.request).then(function (response) {
                cache.put(e.request.url, response.clone());
                return response;
            });
        });
    }
    /* ******************************* */

    else {
        // 非api请求,直接查询cache
        // 如果有cache则直接返回,否则通过fetch请求
        e.respondWith(
            caches.match(e.request).then(function (cache) {
                return cache || fetch(e.request);
            }).catch(function (err) {
                console.log(err);
                return fetch(e.request);
            })
        );
    }
});

Здесь мы также создаем специальное расположение кеша для данных, кэшируемых API, а значение ключа является переменнойapiCacheName. существуетfetchВ случае, если мы сначала сравниваем текущий запрос сcacheRequestUrlsЧтобы определить, нужно ли кэшировать данные запроса XHR, если да, то он будет использоватьсяfetchспособ инициировать запрос к серверной части.

существуетfetch.thenВ приведенном выше примере мы используем запрошенный URL-адрес в качестве ключа для обновления кеша данных, возвращаемых текущим запросом в кеш:cache.put(e.request.url, response.clone()). использовать здесь.clone()Метод копирует копию данных ответа, чтобы мы могли выполнять различные операции с кешем ответов, не беспокоясь об изменении исходной информации ответа.

3.7. Применяйте офлайн-данные XHR для выполнения «офлайн-поиска» для повышения скорости отклика.

Если вы следили за нами, поздравляем, вы в одном последнем шаге от нашего классного офлайн-приложения!

На данный момент наша трансформация Service Worker (sw.js) завершена. В конце концов, есть только то, как стратегически использовать кеш при запросе XHR.Преобразование этой части полностью сосредоточено на index.js, который является нашим интерфейсным скриптом.

Или вернитесь к этой картинке в предыдущем разделе:

图书查询接口的缓存与使用策略

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

Чтобы сделать код более понятным, мы выделим исходную часть запроса XHR как метод.getApiDataRemote()Для вызова и превратить его в Обещание. В целях экономии места часть моего кода относительно проста, поэтому я не буду публиковать его отдельно.

Самой важной частью этого раздела является кэш чтения. Мы знаем, что в Service Worker можно пройтиcachesпеременная для доступа к объекту кеша. К счастью, в нашем внешнем приложении по-прежнему можно передатьcachesдля доступа к кешу. Конечно, чтобы обеспечить постепенную доступность, нам нужно сначала сделать выводы.'caches' in window. Чтобы унифицировать код, я также инкапсулировал кешированные данные запроса в метод Promise:

function getApiDataFromCache(url) {
    if ('caches' in window) {
        return caches.match(url).then(function (cache) {
            if (!cache) {
                return;
            }
            return cache.json();
        });
    }
    else {
        return Promise.resolve();
    }
}

И у нас былоqueryBook()метод, мы будем запрашивать серверные данные, а затем отображать страницу; и теперь мы добавляем рендеринг на основе кеша:

function queryBook() {
    // ……
    // 远程请求
    var remotePromise = getApiDataRemote(url);
    var cacheData;
    // 首先使用缓存数据渲染
    getApiDataFromCache(url).then(function (data) {
        if (data) {
            loading(false);
            input.blur();            
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
        cacheData = data || {};
        return remotePromise;
    }).then(function (data) {
        if (JSON.stringify(data) !== JSON.stringify(cacheData)) {
            loading(false);                
            input.blur();
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
    });
    // ……
}

еслиgetApiDataFromCache(url).thenВерните кешированные данные, используйте их для рендеринга в первую очередь. и когдаremotePromiseДанные возвращаются сcacheDataДля сравнения, страницу нужно перерисовывать только тогда, когда данные несовместимы (обратите внимание, что здесь для простоты грубо используетсяJSON.stringify()метод сравнения объектов). Это имеет два преимущества:

  1. Доступно в автономном режиме. Если мы уже посещали некоторые URL-адреса, даже если мы в автономном режиме, повторение соответствующих операций может по-прежнему отображать страницу в обычном режиме;
  2. Оптимизируйте опыт и улучшите скорость доступа. Время, затрачиваемое на чтение локального кеша, очень мало по сравнению с сетевым запросом, поэтому это даст нашим пользователям ощущение «второго открытия» и «второго ответа».

4. Протестируйте наше приложение с Lighthouse

На данный момент мы завершили две основные функции PWA: манифест веб-приложения и автономное кэширование Service Worker. Эти две функции могут значительно улучшить взаимодействие с пользователем и производительность приложений. Давайте используем Lighthouse в Chrome для обнаружения текущего приложения:

Lighthouse检测结果

Lighthouse检测结果 - PWA

Как видите, с точки зрения оценки PWA наше веб-приложение уже очень хорошо. Единственным вычетом является протокол HTTPS: поскольку это локальная отладка, используется http://127.0.0.1:8085, и он обязательно будет заменен на HTTPS в производстве.

5. Это круто, а как насчет совместимости?

С началом этого года (2018) Apple начала поддерживать Service Worker в iOS 11.3, в сочетании с относительно хорошей скоростью обновления системы Apple, весь PWA совершил крупный прорыв в вопросах совместимости.

Хотя некоторые другие функции в Service Worker (такие как push-уведомление, фоновая синхронизация) не заявлены Apple, манифест веб-приложения и автономное кэширование Service Worker поддерживаются в iOS 11.3. Эти две основные функции не только выдающиеся, но и, кажется, имеют хорошую совместимость и очень подходят для производства.

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

Service Worker兼容性

6. Пишите в конце

Все примеры кода в этой статье можно найти по адресуlearn-pwa/sw-cacheнайти на. Обратите внимание, что после клонирования git переключитесь на ветку sw-cache, где находится весь код в этой статье. Переключите другие партитуры, чтобы увидеть другие версии:

  • базовая ветка: базовая демонстрация проекта, обычное приложение для поиска книг (веб-сайт);
  • Ветвь манифеста: на основе базовой ветки добавьте такие функции, как манифест.Подробнее см.предыдущий постк пониманию;
  • ветка sw-cache: на основе ветки манифеста добавьте функции кэширования и автономные функции;
  • ветка master: последний код приложения.

Если вам нравится или вы хотите узнать больше о PWA, пожалуйста, подпишитесь на меня и подпишитесь«Обучение и практика PWA»серия статей. Я подытожу и разберу вопросы и технические моменты, с которыми столкнулся в процессе изучения PWA, и попрактикуюсь с вами на реальном коде.

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

Серия "Изучение и практика технологии PWA"

использованная литература