Серия статей «Обучение и практика PWA» организована в видеGitbook - Руководство по изучению PWA, текстовое содержимое было синхронизировано сlearning-pwa-ebook. При перепечатке просьба указывать автора и источник.
Эта статья«Обучение и практика PWA»Третья статья цикла. Код в тексте можно найти вsw-cache ветка обучения-pwaНаходить (git clone
Обратите внимание на переход на ветку sw-cache позже).
Как одна из самых популярных технических концепций в настоящее время, PWA имеет большое значение для повышения безопасности, производительности и удобства веб-приложений, и она очень достойна нашего понимания и изучения. Друзья, интересующиеся PWA, могут обратить внимание на серию статей "PWA Learning and Practice".
1. Введение
Одной из удивительных возможностей PWA является то, что они доступны в автономном режиме.
Оффлайн — это лишь одно из его функциональных проявлений, а именно:
- Сделать наше веб-приложение доступным без сети (в автономном режиме) и даже использовать некоторые функции вместо отображения страницы ошибки «нет сетевого подключения»;
- Давайте в случае слабой сети можем использовать кеш для быстрого доступа к нашему приложению, чтобы улучшить работу;
- В нормальных сетевых условиях часть запрошенной пропускной способности также может быть сохранена с помощью различных методов самоконтролируемого кэширования;
- ...
И все это на самом деле благодаря герою PWA——Service Worker.
Итак, что такое сервис-воркеры? Вы можете просто понимать Service Worker как процесс, который работает в фоновом режиме независимо от страницы интерфейса. Поэтому он не блокирует выполнение скриптов браузера и не обращается напрямую к связанным с браузером API-интерфейсам (например, DOM, localStorage и т. д.). Кроме того, он работает даже после выхода из веб-приложения, даже после закрытия браузера. Это похоже на трудолюбивую маленькую пчелу, молча работающую за веб-приложением, обрабатывающую кэширование, отправку, уведомления и синхронизацию. Поэтому, если вы хотите изучить 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:
Здесь необходимо подчеркнуть, что хотя браузер, SW (Service Worker) и серверная служба кажутся расположенными рядом на рисунке, на самом деле браузер (ваше веб-приложение) и SW работают на вашем компьютере. локальная машина, поэтому ПО в этом сценарии похоже на «клиентский прокси».
После понимания основных концепций мы можем конкретно рассмотреть, как мы можем применить эту технологию для реализации веб-приложения, доступного в автономном режиме.
3. Как использовать Service Worker для реализации автономных приложений, открывающихся за считанные секунды.
Помните демонстрационное веб-приложение для поиска книг, которое у нас было раньше? Для тех, кто не знает, можете прочитать эту сериюпервая статья, конечно, можно не обращать внимания на детали и продолжать разбираться в технических принципах.
Верно, на этот раз я все равно буду опираться на него. существуетПредыдущий Добавленный манифестПозже у него уже была своя иконка на рабочем столе и оболочка, очень похожая на Native App, а сегодня я собираюсь сделать его еще круче.
Если вы хотите попрактиковаться с содержанием статьи, вы можете перейти наЗагрузите весь код, который вам нужен, здесь. Не забудьте переключиться на
manifest
Branch, так как содержание этой статьи основано на окончательном коде предыдущей статьи для соответствующей разработки и обновления. В конце концов, наша конечная цель — обновить эту обычную демонстрацию «поиска книг» до 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, при активации срабатывает событие активации.
В следующем примере прослушивается событие установки:
// 监听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, чтобы помочь нам решить, как использовать кеш.
Следующая диаграмма представляет собой простую стратегию:
- Браузер инициирует запрос на запрос различных статических ресурсов (html/js/css/img);
- Service Worker перехватывает запросы браузера и запрашивает текущий кеш;
- Если есть кеш, вернитесь напрямую, end;
- Если кеша нет, пройти
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
На данный момент наше приложение в основном завершило преобразование автономного доступа. Однако, если вы заметили изображение в начале статьи, мы можем не только получить доступ, но и использовать функцию поиска в автономном режиме.
Что тут происходит? На самом деле, секрет этого заключается в том, что это веб-приложение также будет кэшировать копию данных, запрошенных 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()
метод сравнения объектов). Это имеет два преимущества:
- Доступно в автономном режиме. Если мы уже посещали некоторые URL-адреса, даже если мы в автономном режиме, повторение соответствующих операций может по-прежнему отображать страницу в обычном режиме;
- Оптимизируйте опыт и улучшите скорость доступа. Время, затрачиваемое на чтение локального кеша, очень мало по сравнению с сетевым запросом, поэтому это даст нашим пользователям ощущение «второго открытия» и «второго ответа».
4. Протестируйте наше приложение с Lighthouse
На данный момент мы завершили две основные функции PWA: манифест веб-приложения и автономное кэширование Service Worker. Эти две функции могут значительно улучшить взаимодействие с пользователем и производительность приложений. Давайте используем Lighthouse в Chrome для обнаружения текущего приложения:
Как видите, с точки зрения оценки 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. Эти две основные функции не только выдающиеся, но и, кажется, имеют хорошую совместимость и очень подходят для производства.
Более того, как прогрессивное веб-приложение, одной из его наиболее важных функций является автоматическое обновление функций и опыта, когда поддерживается совместимость, а когда оно не поддерживается, оно автоматически откатывает некоторые новые функции. При условии гарантии наших обычных услуг мы постараемся использовать функции браузера для предоставления более качественных услуг.
6. Пишите в конце
Все примеры кода в этой статье можно найти по адресуlearn-pwa/sw-cacheнайти на. Обратите внимание, что после клонирования git переключитесь на ветку sw-cache, где находится весь код в этой статье. Переключите другие партитуры, чтобы увидеть другие версии:
- базовая ветка: базовая демонстрация проекта, обычное приложение для поиска книг (веб-сайт);
- Ветвь манифеста: на основе базовой ветки добавьте такие функции, как манифест.Подробнее см.предыдущий постк пониманию;
- ветка sw-cache: на основе ветки манифеста добавьте функции кэширования и автономные функции;
- ветка master: последний код приложения.
Если вам нравится или вы хотите узнать больше о PWA, пожалуйста, подпишитесь на меня и подпишитесь«Обучение и практика PWA»серия статей. Я подытожу и разберу вопросы и технические моменты, с которыми столкнулся в процессе изучения PWA, и попрактикуюсь с вами на реальном коде.
Наконец, я хотел бы заявить, что код в этой статье используется в качестве демонстрации, которая в основном используется для понимания и изучения технических принципов PWA.Возможны некоторые несовершенства.Поэтому не рекомендуется использовать его непосредственно в производственную среду.
Серия "Изучение и практика технологии PWA"
- Статья 1. Начните свое обучение PWA
- Часть 2. Научитесь использовать Manifest за 10 минут, чтобы сделать ваше веб-приложение более "нативным"
- Статья 3. С сегодняшнего дня сделайте свое веб-приложение доступным в автономном режиме (эта статья)
- Часть 4. Устранение неполадок: устранение ошибок аутентификации при входе в FireBase
- Часть 5. Оставайтесь на связи с вашими пользователями: функции Web Push
- Часть 6. Отладка PWA в Chrome
- Часть 7. Расширенное взаимодействие: использование API уведомлений для напоминаний
- Часть 8. Использование Service Worker для фоновой синхронизации данных
- Часть 9: Проблемы и решения в практике PWA
- Часть 10. Подсказка по ресурсам. Улучшите производительность и удобство загрузки страниц.
- Статья 11. Изучение различных офлайн-стратегий с помощью набора инструментов для работы в офлайн-режиме PWA (написание...)