Service Worker — это должна быть довольно комплексная очистка.

внешний интерфейс браузер Promise

Некоторое время назад, когда я был свободен, я вкратце ознакомился с Service Worker, недавно пересматривал его еще раз и, разобравшись, написал сюда контент, надеюсь, он будет полезен всем.

В то же время, если в статье есть ошибки или неуместные описания, вы можете меня поправить, спасибо.

PS: статья очень длинная и содержит много примеров кода. Можно медленно смотреть :)


вводить

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

Прежде всего, как новая технология, мы должны обратить внимание на ее совместимость в разных браузерах, следующее изcaniuse.comКартина:

各大浏览器对 Service Worker 的 兼容性
На этой картинке видно, что IE и Opera Mini полностью вышли на улицу, в то время как Edge 17 и ниже не поддерживаются в основных браузерах, Safair и IOS Safair только начали поддерживать, а Firefox и Chrome поддерживают хорошо. Таким образом, вы можете использовать его с уверенностью, но лучше сделать суждение.

Что касается Service Worker, студенты, знакомые с Web Worker, могут лучше понять его. По сравнению с Web Worker у него те же точки и разные места.

такой же:

  1. Service Worker работает в рабочем контексте, даНет доступа к DOM, поэтому мы не можем получать узлы DOM в Service Worker и не можем манипулировать элементами DOM в нем;
  2. мы можем пройтиpostMessageИнтерфейс передает данные в другие файлы JS;
  3. Код, работающий в сервис-воркерене будет заблокирован, и не будет блокировать код в JS-файлах других страниц;

Разница в том, что Service Worker — это браузер.процессЭто не поток ядра браузера, поэтому его можно использовать на нескольких страницах после его регистрации и установки, и он не будет уничтожен при закрытии страницы. Поэтому Service Worker очень подходит для расчета сложных данных, которые нужно использовать нескольким страницам — купишь один раз, а вся семья «возвращается».

Еще один момент, который следует отметить, заключается в том, что из соображений безопасностиService Worker можно использовать только в https или в локальной среде localhost..

Зарегистрируйтесь, чтобы установить

Теперь воспользуемся Service Worker.

Если текущий браузер поддерживает Service Worker, в window.navigator будет объект serviceWorker, и мы можем использовать метод register этого объекта для регистрации Service Worker.

Здесь следует отметить, что в процессе использования Service Worker используется множество промисов.Студенты, которые не знакомы с промисами, могут сначала перейти к соответствующим документам. Метод регистрации Service Worker также возвращает Promise .

// index.js
if ('serviceWorker' in window.navigator) {
  navigator.serviceWorker.register('./sw.js', { scope: './' })
    .then(function (reg) {
      console.log('success', reg);
    })
    .catch(function (err) {
      console.log('fail', err);
    });
}

В этом коде мы сначала используем if, чтобы определить, поддерживает ли браузер Service Worker, чтобы избежать ошибок, вызванных несовместимостью браузера.

Метод register принимает два параметра, первый — это путь к файлу сервис-воркера, обратите внимание:Этот путь к файлу указан относительно Origin, вместо каталога текущего JS-файла, вторым параметром является элемент конфигурации Serivce Worker, который является необязательным, более важным являетсяатрибут области действия. Согласно описанию в документе, это подкаталог содержимого, контролируемого сервис-воркером.Путь, представленный этим атрибутом, не может быть выше пути к файлу сервис-воркера.По умолчанию это каталог, в котором находится файл сервис-воркера ; об этом атрибуте документ не говорит очень четко,у меня тоже много вопросов, будут представлены в следующем содержании. Надеюсь, кто-то, кто знает, может помочь мне.

Метод register возвращает Promise. Если регистрация не удалась, вы можете использовать catch для сбора информации об ошибке; если регистрация прошла успешно, вы можете использовать then для получения экземпляра ServiceWorkerRegistration, а заинтересованные студенты могут перейти к документации.

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

// sw.js
this.addEventListener('install', function (event) {
  console.log('Service Worker install');
});

Точно так же Service Worker будет активирован после установки, поэтому мы также можем прослушивать событие активации.

// sw.js
this.addEventListener('activate', function (event) {
  console.log('Service Worker activate');
});

На данный момент мы можем видеть нашего зарегистрированного Service Worker в инструментах разработчика Chorme.

Chrome 开发者工具
По умолчанию сервис-воркеры будуткаждые 24 часаПосле загрузки, если загруженный файл является последним файлом, то он будет перерегистрирован и установлен, но не активирован, он будет активирован, когда больше не будет страниц с использованием старого сервис-воркера.
Service Worker 等到被激活
Нам это очень неудобно разрабатывать, поэтому здесь я проверил именнойUpdate on reload, после проверки мы можем использовать последний файл сервис-воркера каждый раз, когда обновляем страницу.

В одном и том же источнике мы можем зарегистрировать несколько сервис-воркеров. Обратите внимание, однако, что эти сервис-воркеры используютобъем должен быть другим.

if ('serviceWorker' in window.navigator) {
  navigator.serviceWorker.register('./sw/sw.js', { scope: './sw' })
    .then(function (reg) {
      console.log('success', reg);
    })
  navigator.serviceWorker.register('./sw2/sw2.js', { scope: './sw2' })
    .then(function (reg) {
      console.log('success', reg);
    })
}

информационная связь

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

От страницы к сервис-воркеру

Первый — отправить информацию со страницы в Service Worker.

// index.js
if ('serviceWorker' in window.navigator) {
  navigator.serviceWorker.register('./sw.js', { scope: './' })
    .then(function (reg) {
      console.log('success', reg);
      navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("this message is from page");
    });
}

Чтобы гарантировать, что Service Worker может получить информацию, мы отправляем информацию после ее регистрации, и нормальнаяwindow.postMessageМетод использования отличается, чтобы отправить информацию Service Worker, мы должны вызвать экземпляр ServiceWorker.postMessageметод, здесь мы используемnavigator.serviceWorker.controller.

// sw.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page
});

В файле сервис-воркера мы можем напрямуюthisПривяжите к нему событие сообщения, чтобы вы могли получать информацию, отправленную страницей.

Для нескольких сервис-воркеров с разными областями мы также можем отправлять информацию указанному сервис-воркеру.

// index.js
if ('serviceWorker' in window.navigator) {
  navigator.serviceWorker.register('./sw.js', { scope: './sw' })
    .then(function (reg) {
      console.log('success', reg);
      reg.active.postMessage("this message is from page, to sw");
    })
  navigator.serviceWorker.register('./sw2.js', { scope: './sw2' })
    .then(function (reg) {
      console.log('success', reg);
      reg.active.postMessage("this message is from page, to sw 2");
    })
}

// sw.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page, to sw
});

// sw2.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page, to sw 2
});

Обратите внимание, что когда мы регистрируем Service Worker, если используемая область не Origin , тогдаnavigator.serviceWorker.controllerбудет нулевым. В этом случае мы можем использоватьreg.activeпод этим объектомpostMessageметод,reg.activeЭто активация экземпляра Service Worker после регистрации. Однако, поскольку активация Service Worker является асинхронной, когда Service Worker регистрируется в первый раз, Service Worker не будет активирован сразу.reg.activeЕсли он равен нулю, система сообщит об ошибке. Как я это делаю, я возвращаю Promise , опрашиваю внутри Promise и решаю, был ли Service Worker уже активирован.

// index.js
navigator.serviceWorker.register('./sw/sw.js')
    .then(function (reg) {
      return new Promise((resolve, reject) => {
        const interval = setInterval(function () {
          if (reg.active) {
            clearInterval(interval);
            resolve(reg.active);
          }
        }, 100)
      })
    }).then(sw => {
      sw.postMessage("this message is from page, to sw");
    })

  navigator.serviceWorker.register('./sw2/sw2.js')
    .then(function (reg) {
      return new Promise((resolve, reject) => {
        const interval = setInterval(function () {
          if (reg.active) {
            clearInterval(interval);
            resolve(reg.active);
          }
        }, 100)
      })
    }).then(sw => {
      sw.postMessage("this message is from page, to sw2");
    })

От сервис-воркера до страницы

Следующим шагом является отправка информации от сервис-воркера на страницу.В отличие от страницы, отправляющей информацию сервис-воркеру, нам нужно вызвать экземпляр WindowClient.postMessageметод достижения цели. А в JS файле страницы слушайтеnavigator.serviceWorkerсобытие сообщения для получения сообщения.

Самый простой способ — получить экземпляр WindowClient из сообщения, отправленного страницей, с помощью event.source, но этот метод может отправлять информацию только на исходную страницу сообщения.

// sw.js
this.addEventListener('message', function (event) {
  event.source.postMessage('this message is from sw.js, to page');
});

// index.js
navigator.serviceWorker.addEventListener('message', function (e) {
  console.log(e.data); // this message is from sw.js, to page
});

Если вы не хотите ограничиваться этим, вы можете использовать в рабочем файле службыthis.clientsдля загрузки других страниц и отправки сообщений.

// sw.js
this.clients.matchAll().then(client => {
  client[0].postMessage('this message is from sw.js, to page');
})

У меня есть несколько вопросов без ответов об этом методе. В моих экспериментах значение области действия, установленное при регистрации Service Worker, будет влиять на приобретенный клиент.

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

// index.js
navigator.serviceWorker.register('./sw.js', { scope: './sw/' });

// sw.js
this.clients.matchAll().then(client => {
  console.log(client); // []
})

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

Используйте канал сообщений для общения

Еще один хороший способ общения — использование канала сообщений.

// index.js
navigator.serviceWorker.register('./sw.js', { scope: './' })
    .then(function (reg) {
      const messageChannel = new MessageChannel();
      messageChannel.port1.onmessage = e => {
        console.log(e.data); // this message is from sw.js, to page
      }
      reg.active.postMessage("this message is from page, to sw", [messageChannel.por2]);
    })

// sw.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page, to sw
  event.ports[0].postMessage('this message is from sw.js, to page');
});

Использование этого метода позволяет двум концам канала взаимодействовать друг с другом, а не только отправлять информацию источнику сообщения. Например, общение между двумя сервис-воркерами.

// index.jsconst messageChannel = new MessageChannel();

  navigator.serviceWorker.register('./sw/sw.js')
    .then(function (reg) {
      console.log(reg)
      return new Promise((resolve, reject) => {
        const interval = setInterval(function () {
          if (reg.active) {
            clearInterval(interval);
            resolve(reg.active);
          }
        }, 100)
      })
    }).then(sw => {
      sw.postMessage("this message is from page, to sw", [messageChannel.port1]);
    })

  navigator.serviceWorker.register('./sw2/sw2.js')
    .then(function (reg) {
      return new Promise((resolve, reject) => {
        const interval = setInterval(function () {
          if (reg.active) {
            clearInterval(interval);
            resolve(reg.active);
          }
        }, 100)
      })
    }).then(sw => {
      sw.postMessage("this message is from page, to sw2", [messageChannel.port2]);
    })

// sw.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page, to sw
  event.ports[0].onmessage = e => {
    console.log('sw:', e.data); // sw: this message is from sw2.js
  }
  event.ports[0].postMessage('this message is from sw.js');
});

// sw2.js
this.addEventListener('message', function (event) {
  console.log(event.data); // this message is from page, to sw2
  event.ports[0].onmessage = e => {
    console.log('sw2:', e.data); // sw2: this message is from sw.js
  }
  event.ports[0].postMessage('this message is from sw2.js');
});

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

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

Далее идет изюминка, а также самая важная функция, которую может реализовать Service Worker — статическое кэширование ресурсов.

В обычных условиях, когда пользователь открывает веб-страницу, браузер автоматически загружает статические ресурсы, такие как файлы JS и изображения, необходимые для веб-страницы. Мы можем проверить это через опцию «Сеть» в Chrome DevTools.

静态资源
Однако, если пользователь открывает веб-страницу без подключения к Интернету, браузер не может загрузить ресурсы, необходимые для отображения эффекта страницы, и страница не может нормально отображаться.

Мы можем использовать Service Worker с CacheStroage для кэширования статических ресурсов.

Кэшировать указанные статические ресурсы

// sw.js
this.addEventListener('install', function (event) {
  console.log('install');
  event.waitUntil(
    caches.open('sw_demo').then(function (cache) {
      return cache.addAll([
        '/style.css',
        '/panda.jpg',
        './main.js'
      ])
    }
    ));
});

Когда Service Worker установлен, мы можем кэшировать ресурсы, которые прокладывают путь. Имя интерфейса CacheStroage в браузере — caches, мы используемcaches.openспособ создания или открытия существующего кэша;cache.addAllЦель метода — запросить указанные связанные ресурсы и сохранить их в ранее открытом кеше. Из-за загрузки и кэширования ресурсовАсинхронное поведение, поэтому мы собираемся использоватьevent.waitUntilметод, который может гарантировать, что Service Worker не будет установлен до того, как ресурс будет кэширован, чтобы избежать ошибок.

Мы можем увидеть наши кешированные ресурсы из Application's Cache Strogae в Chrome DevTools.

Cache Stroage
Этот метод может кэшировать только указанные ресурсы, что, несомненно, нецелесообразно, поэтому нам нужно кэшировать каждый запрос, инициированный пользователем.

Динамическое кэширование статических ресурсов

this.addEventListener('fetch', function (event) {
  console.log(event.request.url);
  event.respondWith(
    caches.match(event.request).then(res => {
      return res ||
        fetch(event.request)
          .then(responese => {
            const responeseClone = responese.clone();
            caches.open('sw_demo').then(cache => {
              cache.put(event.request, responeseClone);
            })
            return responese;
          })
          .catch(err => {
            console.log(err);
          });
    })
  )
});

Нам нужно прослушивать событие fetch, которое запускается каждый раз, когда пользователь отправляет запрос на сервер. Одно замечание,Путь к странице не может превышать область Service Worker., иначе событие выборки не может быть запущено.

В функции обратного вызова мы используем предоставленный объект событияrespondWithметод, который может перехватить http-запрос, сделанный пользователем, и вернуть пользователю обещание в качестве ответа. Затем мы используем запрос пользователя для сопоставления Cache Stroage, если совпадение успешно, возвращаем ресурс, хранящийся в кеше; если совпадение не удается, запрашиваем у сервера возврат ресурса пользователю и используемcache.putметод для сохранения этих новых ресурсов в кеше. Поскольку потоки запросов и ответов могут быть прочитаны только один раз, мы используемcloneКопия метода сохраняется в кеше, а исходная версия возвращается пользователю

Несколько замечаний:

  1. Когда пользователь заходит на страницу в первый раз, запрос ресурсов предшествует установке сервис-воркера, поэтому статические ресурсы не могут кэшироваться; только когда сервис-воркер установлен и пользователь обращается к странице во второй раз, эти ресурсы будут кэшироваться;
  2. Cache Stroage может кэшировать только статические ресурсы, поэтому он может кэшировать только пользовательские GET-запросы;
  3. Кэш в Cache Stroage не имеет срока действия, но у браузера есть ограничение на его размер, поэтому нам нужно регулярно его чистить;

Для запроса POST, инициированного пользователем, мы также можем выборочно вернуть его, оценивая содержимое тела, переданного в запросе после перехвата.

if(event.request.method === 'POST') {
      event.respondWith(
        new Promise(resolve => {
          event.request.json().then(body => {
            console.log(body); // 用户请求携带的内容
          })
          resolve(new Response({ a: 2 })); // 返回的响应
        })
      )
    }
}

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

Для кэширования статических ресурсовCache Stroageхороший выбор; в то время как для данных мы можем использоватьIndexedDBДля его хранения он также использует кэшированные в IndexDB данные в качестве ответа после перехвата пользовательского запроса.Я не буду здесь подробно рассказывать о содержании, а заинтересованные студенты могут узнать это сами.

Обновить кэш-хранилище

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

this.addEventListener('install', function (event) {
  console.log('install');
  event.waitUntil(
    caches.open('sw_demo_v2').then(function (cache) { // 更换 Cache Stroage
      return cache.addAll([
        '/style.css',
        '/panda.jpg',
        './main.js'
      ])
    }
    ))
});

const cacheNames = ['sw_demo_v2']; // Cahce Stroage 白名单

this.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all[keys.map(key => {
        if (!cacheNames.includes(key)) {
          console.log(key);
          return caches.delete(key); // 删除不在白名单中的 Cache Stroage
        }
      })]
    })
  )
});

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

резюме

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

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


Спасибо за прочтение, пожалуйста, не перепечатывайте без разрешения :)