Тема, которую мы сегодня обсудим, — это обновление ядра Service Worker в PWA, недавнее направление обновления внешнего интерфейса. Это проблема, которую разработчики легко упускают из виду, потому что большинство разработчиков могут быть еще не знакомы с ней.
Сервисный работник со своимАсинхронная установкаа такжеработать непрерывноДве характеристики определяют, что операция обновления для него должна быть очень осторожной. Поскольку он имеет возможность перехватывать и обрабатывать сетевые запросы, он должен соответствовать версии веб-страницы (в основном отправленному запросу) и сервисному работнику, в противном случае новая версия сервисного работника будет обрабатывать старую версию. веб-страницы или проблем с веб-страницей, вызванных последовательностью двух версий Service Worker.
После почти 2 лет разработки популярность PWA в кругах WEB значительно возросла, даже если вы не использовали его, возможно, вы хотя бы слышали о нем. Service Worker (далее ПО) — самая сложная и основная часть PWA, которая в основном задействует Caches API (caches.put
, caches.addAll
и т. д.), Service Worker API (self.addEventListener
, self.skipWaiting
и т. д.) и API регистрации (reg.installing
, reg.onupdatefound
Ждать).
Эта статья уже не является основой научно-популярного ПО, я в основном хочу рассказать здесь об обновлении ПО. Полной синхронизации между ПО и страницей добиться непросто. До этого я предполагаю, что вы уже знаете:
- Роль SW
- Способ регистрации ПО (
navigator.serviceWorker.register
) - Жизненный цикл ПО (установка -> ожидание -> активация -> выборка)
Два табу для организации SW
Прежде чем мы начнем формально говорить о механизме обновления ПО, необходимо обозначить два табу при организации ПО. При применении ПО к собственному сайту мы хотим избежать этих двух методов, а именно:
Не давайте service-worker.js другое имя
Как правило, для статических файлов популярно давать им уникальное имя каждый раз, когда они создаются, на основе их содержимого (или случайных факторов, таких как время в то время), напримерindex.[hash].js
. Поскольку эти файлы изменяются нечасто, а в сочетании с длительным принудительным кэшированием время, необходимое для доступа к ним, может быть значительно сокращено.
К сожалению, для SW этот подход не подходит. Предположим, проект
-
титульная страница
index.html
, который содержит абзац внизу<script>
для регистрацииservice-worker.v1.js
. -
Для скорости или доступности в автономном режиме это
service-worker.v1.js
положитindex.html
кешировать это. -
После обновления обновления, теперь
index.html
нужно соответствоватьservice-worker.v2.js
используется, поэтому в нижней части исходного кода<script>
Зарегистрированный адрес был изменен в . -
Однако мы обнаружили, что когда пользователи посещают сайт из-за старой версии
service-worker.v1.js
Роль извлечения из кешаindex.html
все еще цитируетсяv1
, а не наша ссылка после обновленияv2
.
Это происходит потому, чтоv1
обновитесь доv2
зависит отindex.html
Адрес ссылки меняется, но сам кэшируется. Как только эта дилемма будет достигнута, если пользователь вручную не очистит кеш, удалитеv1
, иначе мы ничего не можем сделать.
такservice-worker.js
Необходимо использовать одно и то же имя, ничто не может изменить имя файла.
Не устанавливайте кеш для service-worker.js
Причина аналогична первому пункту, но также для предотвращения того, что, когда браузеру необходимо запросить новую версию ПО, этого нельзя будет добиться из-за вмешательства кеша. В конце концов, мы не можем попросить пользователя очистить кеш. Итак, дайте SW и связанный с ним JS (например,sw-register.js
, если независимы) установитьCache-control: no-store
является относительно безопасным.
состояние ожидания ПО
Регистрация ПО осуществляется черезnavigator.serviceWorker.register(swUrl, options)
проведен метод. Но в отличие от обычного кода JS, на самом деле есть две разные ситуации, когда это предложение выполняется в представлении браузера:
-
Если активного ПО еще нет, просто установите и активируйте его напрямую.
-
Если установлено существующее ПО, переключитесь на новое
swUrl
Сделайте запрос, получите контент и сравните его с существующим ПО. Если разницы нет, завершите установку. Если есть разница, установите новую версию ПО (выполнитьinstall
этапе), затем заставьте его ждать (введитеwaiting
сцена)
В это время на текущей странице будет два SW, но статус будет другим, как показано на следующем рисунке:
- Если старое ПО контролирует все страницызакрыть все, старое ПО перестает работать и вместо него активирует новое ПО (выполняет
activated
stage), чтобы занять страницу.
Это относительно мягкая и безопасная практика, эквивалентная естественному удалению старых и новых версий. Но ведь закрытие всех страниц — это выбор пользователя, а не контроль программиста. Кроме того, нам нужно обратить внимание на один момент: из-за внутреннего принципа реализации браузера, когда страница переключается или обновляется сама, браузер ждет, пока новая страница закончит рендеринг, прежде чем уничтожить старую страницу. Это означает, что существует сосуществующее время пересечения между старой и новой страницами.Следовательно, простое переключение страниц или обновление не может привести к обновлению ПО., старое ПО все еще занимает страницу, а новое ПО все еще ждет. (Это также требует, чтобы при обнаружении обновлений ПО, в дополнение кonupdatefound
Кроме того, также необходимо судить о том, находится ли ПО в состоянии ожидания, т.е.reg.waiting
он существует. Однако это выходит за рамки данной статьи и не будет расширяться)
Предположим, мы делаем крупное обновление и хотим, чтобы новое ПО заняло страницу как можно скорее, что нам делать?
Способ 1: пропустить ожидание
При возникновении чрезвычайных ситуаций легко представить себе «очередь» для решения проблемы, которая в реальной жизни используется специальными транспортными средствами, такими как машины скорой помощи и пожарные машины. ПО также предоставляет программистам возможность реализовать такую схему, т.е. в рамках ПОself.skipWaiting()
метод.
self.addEventListener('install', event => {
self.skipWaiting()
// 预缓存其他静态内容
})
Это позволяет новому ПО «вскакивать в очередь» и заставлять его немедленно заменить старое ПО для управления всеми страницами, при этом старое ПО «убивается», что просто и грубо. Лавас сначала использовал это решение, потому что его было слишком легко придумать и слишком легко реализовать, а соблазн был велик.
К сожалению, в этом плане есть скрытые опасности. Мы представляем себе следующий сценарий:
-
страница
index.html
установленыsw.v1.js
(фактический адрес г.sw.js
, просто чтобы четко различать такие выражения) -
Пользователь открывает эту страницу и все сетевые запросы проходят через
sw.v1.js
, загрузка страницы завершена. -
Из-за особенности асинхронной установки ПО, как правило, когда браузер простаивает, он выполнит предложение
navigator.serviceWorker.register
. В это время браузер обнаружилsw.v2.js
существует, поэтому установите его и заставьте его ждать. -
Но потому что
sw.v2.js
существуетinstall
этап имеетself.skipWaiting()
, поэтому браузер вынужден уйти в отставкуsw.v1
, но пустьsw.v2
Активируйте и управляйте страницей прямо сейчас. -
Пользователь здесь
index.html
При наличии сетевого запроса на последующую работуsw.v2.js
обработанный.
Очевидно, та же страница, первая половина запроса сделанаsw.v1.js
контроль, в то время как вторая половина контролируетсяsw.v2.js
контроль. Несоответствие между ними может легко привести к проблемам и даже сбоям с ошибками. Напримерsw.v1.js
предварительно кэшированныйv1/image.png
, и когдаsw.v2.js
При активации старые версии предварительного кеша обычно удаляются и вместо этого добавляются, например.v2/image.png
кеш. Поэтому, если сетевое окружение пользователя нестабильно или отключено, или если используется стратегия кэширования, такая как CacheFirst, браузер найдетv1/image.png
В кеше не нашел. Даже если сетевое окружение нормальное, браузер должен отправить еще один запрос, чтобы получить эти уже закешированные ресурсы, что тратит время и пропускную способность. Кроме того, ошибки, вызванные такими ПО, трудно воспроизвести и трудно отладить, что добавляет нестабильности программе.
Используйте это решение, только если вы не можете гарантировать, что одна и та же страница будет по-прежнему работать, если две версии ПО обрабатываются одна за другой.
Способ 2: skipWaiting + обновление
Проблема с методом 1 заключается в том, что skipWaiting приводит к тому, что страница управляется двумя SW последовательно. Поскольку новое ПО было установлено, это означает, что старое ПО устарело, поэтому можно сделать вывод, что страницы, обработанные старым ПО, также устарели. Что нам нужно сделать, так это позволить новому ПО обрабатывать страницу от начала до конца, чтобы мы могли быть последовательными и удовлетворять наши потребности. Поэтому мы подумали об обновлении и отбрасывании страниц, которые уже были обработаны.
В месте регистрации ПО (не внутри ПО) можно контролироватьcontrollerchange
событие, чтобы узнать, изменилось ли ПО, управляющее текущей страницей, следующим образом:
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
})
Когда вы обнаружите, что ПО, которое контролирует вас, изменилось, обновите себя и позвольте новому ПО управлять вами от начала до конца, что обеспечит согласованность данных. Это верно, но внезапное обновление может прервать работу пользователя и вызвать дискомфорт. Источником обновления является смена ПО, смена ПО происходит из браузера для установки нового ПО.skipWaiting
, поэтому большая часть этого обновления происходит в течение нескольких секунд после загрузки страницы. Как только пользователь начинает просматривать контент или заполнять информацию, он сталкивается с необъяснимым обновлением, которое может разбить клавиатуру.
Также есть два предостережения:
Обновление ПО и обновление страницы
Говоря о состоянии ожидания ПО, я обычно говорилПростое переключение страниц или обновление не может привести к обновлению ПО., и здесь опять же подразумевается обновление ПО и обновление страницы, что неизбежно приводит к путанице.
Давайте взглянем на логику, но она не сложная:
-
Refresh не может сделать обновление ПО, то есть старое ПО не выйдет, а новое ПО не активируется.
-
Этот метод через
skipWaiting
Заставить SW чередовать старое и новое. После завершения чередования пройтиcontrollerchange
Прислушайтесь к изменениям, а затем выполните обновление.
Следовательно, причина и следствие двух противоположны и не противоречат друг другу.
Избегайте бесконечного обновления
При использовании функции «Обновление при перезагрузке» в Chrome Dev Tools использование приведенного выше кода приведет к бесконечному самообновлению. Чтобы компенсировать это, вам нужно добавить флаг для оценки следующим образом:
let refreshing = false
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) {
return
}
refreshing = true;
window.location.reload();
});
Способ 3: Дайте пользователю подсказку
В методе 2 есть идея, которую стоит изучить, а именно: «инициировать события через изменения ПО и выполнять обновление в мониторинге событий». Но обновление страницы без предупреждения действительно неприемлемо, поэтому давайте еще раз улучшим ее, дадим пользователю подсказку, пусть он нажмет, чтобы обновить ПО, и вызовет обновление, разве это не красиво?
Общий процесс таков:
-
Когда браузер обнаружит, что есть новое (другое) ПО, установите его и подождите, пока запускается
updatefound
мероприятие -
Слушаем событие и всплывает подсказка, чтобы спросить пользователя, хочет ли он обновить ПО.
-
Если пользователь подтвердит, отправьте сообщение ожидающему ПО с просьбой выполнить
skipWaiting
и получить контроль -
Вызвано изменением ПО
controllerchange
событие, мы можем обновить страницу в обратном вызове этого события
Здесь стоит отметить шаг 3. Потому что код ответа на клик пользователя находится в обычном коде JS, иskipWaiting
Звонок в коде SW, так что обоим нужно еще разpostMessage
общаться.
Что касается кода, давайте пошагово рассмотрим реализацию Lavas:
Шаг 1 выполняется браузером и не имеет к нам никакого отношения. Шаг 2 требует, чтобы мы слушали этоupdatefound
События, которые необходимо отслеживать через объект Registration, возвращаются при регистрации ПО, поэтому обычно мы можем слушать непосредственно при регистрации, чтобы избежать необходимости получения этого объекта позже, что увеличит сложность.
function emitUpdate() {
var event = document.createEvent('Event');
event.initEvent('sw.update', true, true);
window.dispatchEvent(event);
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function (reg) {
if (reg.waiting) {
emitUpdate();
return;
}
reg.onupdatefound = function () {
var installingWorker = reg.installing;
installingWorker.onstatechange = function () {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
emitUpdate();
}
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
}
Здесь мы отправляем событие (с именемsw.update
,родыemitUpdate()
метод) для оповещения снаружи, это связано с тем, что всплывающая подсказка является отдельным компонентом и отображать ее прямо здесь не удобно. Конечно, если ваше приложение имеет другую структуру, вы можете изменить ее самостоятельно. Короче говоря, найдите способ отобразить всплывающую подсказку или просто используйтеconfirm
Просто попросите пользователя подтвердить.
Шаг 3 должен обрабатывать клики пользователей и связываться с SW. Код для работы с кликами относительно прост и повторяться не буду.Вот основной список кодов связи с ПО:
try {
navigator.serviceWorker.getRegistration().then(reg => {
reg.waiting.postMessage('skipWaiting');
});
} catch (e) {
window.location.reload();
}
Примечание черезreg.waiting
КожидающийСообщение ПО вместо сообщения текущему старому ПО. Часть SW отвечает за получение сообщения и выполнение логики «отсечки очереди».
// service-worker.js
// SW 不再在 install 阶段执行 skipWaiting 了
self.addEventListener('message', event => {
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
})
Шаг 4 аналогичен способу 2, но такжеnavigator.serviceWorker
мониторcontrollerchange
событие для выполнения операции обновления, и код здесь повторяться не будет.
Недостатки третьего способа.
Судя по текущим результатам, этот метод учитывает быстрое обновление и взаимодействие с пользователем и является лучшим решением на данный момент. Но у него есть и недостатки.
Недостаток 1: Слишком сложно
-
По количеству файлов задействовано не менее 2-х файлов (регистрировать ПО, мониторить
updatefound
И обрабатывать презентацию и щелкать DOM в обычном JS, слушать информацию и выполнятьskipWaiting
Это в коде ПО), это не говоря уже о том, что мы можем разделить щелчок дисплея DOM и регистрацию ПО на два файла для разделения кода на модули. -
С точки зрения типов API, это включает в себя Registration API (регистрация, мониторинг
updatefound
и при отправке сообщений), жизненный цикл ПО и API (skipWaiting
) и обычный DOM API -
Методы тестирования и ОТЛАДКИ сложны, и необходимо создать как минимум две версии среды ПО, а метод ОТЛАДКА ПО должен быть хорошо освоен.
Особенно для того, чтобы добиться «вырезания очереди» ПО после клика пользователя, необходимо отреагировать на клик DOM, отправить сообщение в ПО, а затем действовать в ПО. Эта цепочка операций охватывает несколько JS, что очень неинтуитивно и сложно. С этой целью босс Google Джейк Арчибальд предложил W3Cпредложение, что упрощает процесс, позволяя в простом JS передаватьreg.waiting.skipWaiting()
Сократите очередь напрямую, вместо того, чтобы работать только внутри ПО.
Недостаток 2: обновления должны выполняться через JS
Это означает, что обновление ПО может быть выполнено только пользователем, нажав кнопку на панели уведомлений и используя JS, в то время какЭто невозможно сделать с помощью кнопки обновления браузера.. На самом деле это проблема дизайна браузера, а не проблема самого решения.
Но, с другой стороны, если браузер помогает нам выполнить вышеуказанные операции, становится разрешено принудительно обновлять другие вкладки, обновляя одну вкладку.При условии, что текущий браузер использует вкладку в качестве единицы, такого перекрестного управления нет. , Безопасный и непонятный.
Единственная возможная оптимизация заключается в том, что когда на странице есть только одна вкладка, управляемая ПО, обновление этой вкладки может сэкономить нам много операций, если ПО можно обновить, и это не вызовет проблемы перекрестного контроля. Просто это может увеличить стоимость оценки браузера и потерять красоту согласованности работы.Можно только сказать, что это также может быть долгосрочной мечтой.
постскриптум
Функции ПО достаточно мощные, но в то же время оно задействует относительно большое количество API, это мощная технология, требующая значительных затрат на обучение (зарубежные статьи называют это ракетостроением). Обновление ПО очень важно для сайтов, использующих ПО, но, как упоминалось выше, его решение относительно сложное, намного превышающее сложность других распространенных базовых технологий интерфейса (таких как DOM API, операции JS, замыкания и т. д.). Однако прошло всего два или три года с момента запуска SW, и он все еще находится в стадии разработки. Я считаю, что благодаря постоянному пересмотру W3C и постоянному использованию круга переднего плана будет более краткое, более автоматическое и более полное решение.В то время мы сможем использовать ПО так же просто, как использовать ДОМ API.
Справочная статья
-
Два улучшения, касающиеся обновлений Service Worker- Источник для написания этой статьи
-
The Service Worker Lifecycle— Одна из научных статей о Service Worker от Google Developers.
-
How to Fix the Refresh Button When Using Service Workers- Упоминает четвертый метод, но все еще имеет проблемы с совместимостью в Firefox.