O pacote workbox-window
é um conjunto de módulos que precisam ser executados no
contexto window
, ou seja, dentro das suas páginas da Web. Eles complementam os outros pacotes de
caixa de trabalho executados no service worker.
Os principais recursos/metas do workbox-window
são:
- Simplificar o processo de registro e atualizações do service worker, ajudando os desenvolvedores a identificar os momentos mais críticos no ciclo de vida do service worker e facilitando a resposta a eles.
- Para ajudar a evitar que os desenvolvedores cometam os erros mais comuns.
- Ativar uma comunicação mais fácil entre o código em execução no service worker e o código em execução na janela.
Como importar e usar o workbox-window
O ponto de entrada principal do pacote workbox-window
é a classe Workbox
, e você pode importá-la para seu código da CDN ou usando qualquer uma das ferramentas de agrupamento JavaScript mais conhecidas.
Usar nossa CDN
A maneira mais fácil de importar a classe Workbox
no seu site é pela nossa CDN:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
Observe que este exemplo usa <script type="module">
e a instrução import
para
carregar a classe Workbox
. Embora você possa pensar que precisa transcompilar esse
código para fazê-lo funcionar em navegadores mais antigos, isso não é realmente necessário.
Todos os principais navegadores que oferecem suporte ao service worker também são compatíveis com módulos JavaScript nativos. Portanto, não há problema em disponibilizar esse código para qualquer navegador, já que navegadores mais antigos o ignoram.
Como carregar o Workbox com bundlers do JavaScript
Nenhuma ferramenta é necessária para usar workbox-window
. Porém, se a
infraestrutura de desenvolvimento já incluir um bundler como
webpack ou Rollup que funcione
com dependências npm, será possível usá-los para
carregar workbox-window
.
A primeira etapa é
instalar
workbox-window
como uma dependência do seu aplicativo:
npm install workbox-window
Em seguida, em um dos arquivos JavaScript do aplicativo, na caixa de trabalho import
,
referenciando o nome do pacote workbox-window
:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Se o bundler for compatível com a divisão de código por meio de instruções de importação dinâmica, você também poderá carregar condicionalmente workbox-window
, o que ajudará a reduzir o tamanho do pacote principal da página.
Embora workbox-window
seja muito pequeno, não há motivo para ele ser carregado com a lógica de aplicativo principal do site, já que os service workers, pela própria natureza, são um aprimoramento progressivo.
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
Conceitos avançados de agrupamento
Ao contrário dos pacotes do Workbox executados no service worker, os arquivos de build
referenciados pelos campos
main
e
module
de workbox-window
em
package.json
são transcompilados para ES5. Isso as torna compatíveis com as
ferramentas de build atuais. Algumas delas não permitem que os desenvolvedores transcompilem nada das
dependências de node_module
.
Se o sistema de build permitir que você transcompile as dependências (ou se não precisar transcompilar nenhum código), é melhor importar um arquivo de origem específico em vez do próprio pacote.
Veja as várias maneiras de importar Workbox
, além de uma explicação sobre o que cada uma delas retornará:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
Exemplos
Depois de importar a classe Workbox
, use-a para se registrar e
interagir com seu service worker. Confira alguns exemplos de como usar
Workbox
no seu aplicativo:
Registrar um service worker e notificar o usuário na primeira vez que ele estiver ativo
Muitos service workers de aplicativos da Web podem pré-armazenar recursos em cache para que o app funcione off-line nos carregamentos de página subsequentes. Em alguns casos, pode fazer sentido informar ao usuário que o app agora está disponível off-line.
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
Notificar o usuário se um service worker tiver sido instalado, mas estiver travado aguardando a ativação
Quando uma página controlada por um service worker existente registra um novo service worker, por padrão, esse service worker não será ativado até que todos os clientes controlados pelo service worker inicial tenham sido totalmente descarregados.
Isso é uma fonte comum de confusão para desenvolvedores, especialmente nos casos em que recarregar a página atual não faz com que o novo service worker seja ativado.
Para ajudar a minimizar a confusão e esclarecer quando essa situação está acontecendo,
a classe Workbox
fornece um evento waiting
que pode ser detectado:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
Notifica o usuário sobre atualizações de cache do pacote workbox-broadcast-update
.
O pacote workbox-broadcast-update
é uma ótima maneira de disponibilizar conteúdo do cache (para entrega rápida), além de
informar o usuário sobre atualizações desse conteúdo (usando a
estratégia obsoleta enquanto revalida).
Para receber essas atualizações da janela, é possível detectar eventos message
do
tipo CACHE_UPDATED
:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
Enviar ao service worker uma lista de URLs para armazenar em cache
Para alguns aplicativos, é possível saber todos os recursos que precisam ser armazenados em cache no momento da criação. No entanto, alguns aplicativos têm páginas completamente diferentes, com base no URL que o usuário acessa primeiro.
Para apps na última categoria, faz sentido armazenar em cache apenas os recursos
necessários para a página específica que o usuário visitou. Ao usar o
pacote workbox-routing
, você pode
enviar ao roteador uma lista de URLs para armazenar em cache. Esses URLs vão ser armazenados em cache de acordo
com as regras definidas no próprio roteador.
Este exemplo envia uma lista de URLs carregados pela página para o roteador sempre que um novo service worker é ativado. Não há problema em enviar todos os URLs, porque apenas aqueles que correspondem a uma rota definida no service worker serão armazenados em cache:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
Momentos importantes do ciclo de vida do service worker
O ciclo de vida do service worker é complexo e pode ser difícil de entender. Parte da complexidade é que ele precisa lidar com todos os casos extremos para todos os usos possíveis do service worker, por exemplo, registrar mais de um service worker, registrar diferentes service workers em frames diferentes, registrar service workers com nomes diferentes etc.
No entanto, a maioria dos desenvolvedores que implementam o service worker não precisa se preocupar com todos esses casos extremos, porque o uso deles é bastante simples. A maioria dos desenvolvedores registra apenas um service worker por carregamento de página e não altera o nome do arquivo de service worker que implanta no servidor.
A classe Workbox
adota essa visualização mais simples do ciclo de vida do service worker
dividindo todos os registros dele em duas categorias: o service worker registrado
da instância e um service worker externo:
- Service worker registrado: um service worker que começou a instalar como
resultado da instância
Workbox
chamandoregister()
ou do service worker já ativo se a chamada deregister()
não tiver acionado um eventoupdatefound
no registro. - Service worker externo: um service worker que começou a instalação
independente da instância
Workbox
que chamaregister()
. Isso geralmente acontece quando um usuário tem uma nova versão do site aberta em outra guia. Quando um evento se origina de um service worker externo, a propriedadeisExternal
do evento vai ser definida comotrue
.
Com esses dois tipos de service workers em mente, veja um detalhamento de todos os momentos importantes do ciclo de vida de service workers, além de recomendações de desenvolvedores sobre como lidar com eles:
Na primeira vez que um service worker é instalado
É importante tratar a primeira vez que um service worker é instalado de maneira diferente de todas as atualizações futuras.
Em workbox-window
, é possível diferenciar entre a primeira instalação
da versão e as atualizações futuras verificando a propriedade isUpdate
em qualquer um
dos eventos a seguir. Na primeira instalação, isUpdate
será
false
.
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
Quando uma versão atualizada do service worker é encontrada
Quando um novo service worker começar a ser instalado, mas uma versão atual estiver controlando a página, a propriedade isUpdate
de todos os eventos a seguir será true
.
A forma como você reage a essa situação normalmente é diferente da primeira instalação, porque é preciso gerenciar quando e como o usuário recebe essa atualização.
Quando uma versão inesperada do service worker é encontrada
Às vezes, os usuários mantêm o site aberto em uma guia em segundo plano por muito tempo. Eles podem até abrir uma nova guia e navegar até seu site sem perceber que ele já está aberto em uma guia em segundo plano. Nesses casos, é possível ter duas versões do site em execução ao mesmo tempo, e isso pode apresentar alguns problemas interessantes para você como desenvolvedor.
Considere um cenário em que você tem a guia A executando a v1 do site e a B executando a v2. Quando a aba B for carregada, ela será controlada pela versão do service worker enviada com a v1, mas a página retornada pelo servidor, se estiver usando uma estratégia de armazenamento em cache que prioriza a rede para as solicitações de navegação, conterá todos os recursos da v2.
No entanto, isso geralmente não é um problema para a aba B, já que, quando você escreveu o código v2, estava ciente de como o código v1 funcionava. No entanto, isso pode ser um problema para a guia A, já que o código v1 não poderia ter previsto quais mudanças o código v2 poderia introduzir.
Para lidar com essas situações, workbox-window
também envia eventos
de ciclo de vida ao detectar uma atualização de um service worker "externo", em que
externo significa qualquer versão que não seja a versão que a instância Workbox
registrada atualmente.
A partir do Workbox v6 e versões mais recentes, esses eventos são equivalentes aos eventos documentados
acima, com a adição de uma propriedade isExternal: true
definida em cada objeto
de evento. Caso seu aplicativo da Web precise implementar uma lógica específica para processar um service worker "externo", você pode verificar essa propriedade nos seus manipuladores de eventos.
Como evitar erros comuns
Um dos recursos mais úteis que o Workbox oferece é a geração de registros do desenvolvedor. Isso
é importante principalmente para workbox-window
.
Sabemos que desenvolver com o service worker muitas vezes pode ser confuso e, quando as coisas acontecem de maneira contrário ao esperado, pode ser difícil saber o motivo.
Por exemplo, quando você faz uma alteração no service worker e atualiza a página, talvez não veja essa mudança no navegador. O motivo mais provável para isso é que o service worker ainda está aguardando a ativação.
No entanto, ao registrar um service worker na classe Workbox
, você vai ser
informado sobre todas as mudanças de estado do ciclo de vida no console para desenvolvedores, o que pode
ajudar a depurar por que as coisas não estão como esperado.
Além disso, um erro comum que os desenvolvedores cometem ao usar o service worker pela primeira vez é registrar um no escopo errado.
Para evitar que isso aconteça, a classe Workbox
avisará se a
página que registra o service worker não estiver no escopo desse service worker. Ele
também vai avisar você nos casos em que seu service worker estiver ativo, mas ainda não
controlar a página:
Comunicação da janela com o service worker
O uso mais avançado do service worker envolve muitas mensagens entre o
service worker e a janela. A classe Workbox
também ajuda nisso
fornecendo um método messageSW()
, que vai enviar postMessage()
ao service worker registrado
da instância e aguardar uma resposta.
Embora seja possível enviar dados ao service worker em qualquer formato, o formato compartilhado por todos os pacotes do Workbox é um objeto com três propriedades (as duas últimas sendo opcionais):
As mensagens enviadas pelo método messageSW()
usam MessageChannel
para que o destinatário
possa responder a elas. Para responder a uma mensagem, chame
event.ports[0].postMessage(response)
no listener de eventos de mensagem. O método messageSW()
retorna uma promessa que será resolvida para qualquer response
com que você responder.
Veja a seguir um exemplo de como enviar mensagens da janela para o service worker e
receber uma resposta. O primeiro bloco de código é o listener de mensagens no
service worker, e o segundo usa a classe Workbox
para enviar a
mensagem e aguardar a resposta:
Código em sw.js:
const SW_VERSION = '1.0.0';
addEventListener('message', event => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Código em main.js (em execução na janela):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Como gerenciar incompatibilidades de versão
O exemplo acima mostra como é possível implementar a verificação da versão do service worker na janela. Esse exemplo é usado porque, quando você envia mensagens entre a janela e o service worker, é essencial estar ciente de que o service worker pode não estar executando a mesma versão do site que o código da página está executando, e a solução para lidar com esse problema é diferente dependendo se a sua página é veiculada primeiro pela rede ou pelo cache.
Prioridade da rede
Ao veicular a rede de páginas primeiro, os usuários sempre recebem a versão mais recente do HTML no servidor. No entanto, na primeira vez que um usuário acessar seu site (depois da implantação de uma atualização), o HTML recebido será para a versão mais recente, mas o service worker em execução no navegador será uma versão instalada anteriormente (possivelmente muitas versões antigas).
É importante entender essa possibilidade porque, se o JavaScript carregado pela versão atual da página enviar uma mensagem para uma versão mais antiga do service worker, essa versão pode não saber como responder (ou poderá responder com um formato incompatível).
Por isso, é recomendável sempre criar uma versão do service worker e verificar se há versões compatíveis antes de fazer qualquer trabalho crítico.
Por exemplo, no código acima, se a versão do service worker retornada por essa
chamada messageSW()
for mais antiga que a versão esperada, é recomendável aguardar
até que uma atualização seja encontrada (o que acontece ao chamar register()
). Nesse
momento, é possível notificar o usuário ou uma atualização ou
pular manualmente a fase de espera
para ativar o novo service worker imediatamente.
Cache primeiro
Ao contrário de quando você exibe páginas com priorização da rede, ao exibir as páginas em cache primeiro, você sabe que a página inicialmente vai ser sempre a mesma versão que o service worker, porque foi isso que a disponibilizou. Como resultado, é seguro usar messageSW()
imediatamente.
No entanto, se uma versão atualizada do seu service worker for encontrada e ativada quando sua página chamar register()
(ou seja, você pular a fase de espera intencionalmente), talvez não seja mais seguro enviar mensagens para ela.
Uma estratégia para gerenciar essa possibilidade é usar um esquema de controle de versões que permita diferenciar entre atualizações interruptivas e não interruptivas. No caso de uma atualização interruptiva, você saberá que não é seguro enviar uma mensagem para o service worker. Em vez disso, avise o usuário que ele está executando uma versão antiga da página e sugira que ele recarregue para fazer a atualização.
Assistente "Pular a espera"
Uma convenção de uso comum para mensagens de janela para service worker é enviar uma mensagem {type: 'SKIP_WAITING'}
para instruir um service worker instalado a pular a fase de espera e ativar.
A partir do Workbox v6, o método messageSkipWaiting()
pode ser usado para enviar uma
mensagem {type: 'SKIP_WAITING'}
ao service worker em espera associado ao
registro atual dele. Ele não fará nada silenciosamente se não houver um
service worker em espera.
Tipos
Workbox
Uma classe para ajudar a processar o registro, as atualizações e a reação a eventos de ciclo de vida de service worker.
Propriedades
-
construtor
void
Cria uma nova instância do Workbox com um URL de script e opções de service worker. O URL e as opções do script são os mesmos usados ao chamar navigator.serviceWorker.register(scriptURL, options).
A função
constructor
tem esta aparência:(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
-
scriptURL
string | TrustedScriptURL
O script do service worker associado a essa instância. É possível usar um
TrustedScriptURL
. -
registerOptions
objeto opcional
-
retorna
-
-
ativo
Promise<ServiceWorker>
-
controle
Promise<ServiceWorker>
-
getSW
void
Resolve com uma referência a um service worker que corresponde ao URL do script dessa instância, assim que ele estiver disponível.
Se, no momento do registro, já houver um service worker ativo ou em espera com um URL de script correspondente, ele será usado. O service worker em espera terá precedência sobre o service worker ativo se ambos forem correspondentes, já que o service worker em espera teria sido registrado mais recentemente. Se não houver service worker ativo ou em espera no momento do registro, a promessa não vai ser resolvida até que uma atualização seja encontrada e comece a instalar. Nesse momento, o service worker de instalação é usado.
A função
getSW
tem esta aparência:() => {...}
-
retorna
Promise<ServiceWorker>
-
-
messageSW
void
Envia o objeto de dados transmitido para o service worker registrado por essa instância (via
workbox-window.Workbox#getSW
) e resolve com uma resposta (se houver).Uma resposta pode ser definida em um gerenciador de mensagens no service worker chamando
event.ports[0].postMessage(...)
, o que resolverá a promessa retornada pormessageSW()
. Se nenhuma resposta for definida, a promessa nunca será resolvida.A função
messageSW
tem esta aparência:(data: object) => {...}
-
dados
objeto
Um objeto a ser enviado ao service worker
-
retorna
Prometa<qualquer>
-
-
messageSkipWaiting
void
Envia uma mensagem
{type: 'SKIP_WAITING'}
para o service worker que está atualmente no estadowaiting
associado ao registro atual.Se não houver registro atual ou se nenhum service worker for
waiting
, chamar esse valor não terá efeito.A função
messageSkipWaiting
tem esta aparência:() => {...}
-
register
void
Registra um service worker para o URL do script de instâncias e as opções do service worker. Por padrão, esse método atrasa o registro até que a janela seja carregada.
A função
register
tem esta aparência:(options?: object) => {...}
-
do modelo.
objeto opcional
-
próximo
booleano opcional
-
-
retorna
Promise<ServiceWorkerRegistration>
-
-
update
void
Verifica se há atualizações do service worker registrado.
A função
update
tem esta aparência:() => {...}
-
retorna
Promise<void>
-
WorkboxEventMap
Propriedades
-
Ativado
-
ativando
-
controle
-
instalado
-
installing
-
mensagem
-
redundantes
-
aguardando
WorkboxLifecycleEvent
Propriedades
-
isExternal
booleano opcional
-
isUpdate
booleano opcional
-
originalEvent
Evento opcional
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
typeOperator
WorkboxLifecycleEventMap
Propriedades
-
Ativado
-
ativando
-
controle
-
instalado
-
installing
-
redundantes
-
aguardando
WorkboxLifecycleWaitingEvent
Propriedades
-
isExternal
booleano opcional
-
isUpdate
booleano opcional
-
originalEvent
Evento opcional
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
typeOperator
-
wasWaitingBeforeRegister
booleano opcional
WorkboxMessageEvent
Propriedades
-
dados
qualquer um
-
isExternal
booleano opcional
-
originalEvent
Evento
-
ports
typeOperator
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
Métodos
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
Envia um objeto de dados para um service worker via postMessage
e é resolvido com
uma resposta (se houver).
Uma resposta pode ser definida em um gerenciador de mensagens no service worker
chamando event.ports[0].postMessage(...)
, o que resolverá a promessa
retornada por messageSW()
. Se nenhuma resposta for definida, a promessa não será
resolvida.
Parâmetros
-
sw
ServiceWorker
O service worker para onde enviar a mensagem.
-
dados
objeto
Um objeto a ser enviado ao service worker.
Retorna
-
Prometa<qualquer>