Como criar um script do service worker

Este codelab faz parte do curso Developing Progressive Web Apps, desenvolvido pela equipe de treinamento do Google Developers. Você aproveitará mais o curso se fizer os codelabs em sequência.

Para ver todos os detalhes sobre o curso, consulte a Visão geral do desenvolvimento de Progressive Web Apps.

Introdução

Este laboratório mostra como criar um service worker simples e explica o ciclo de vida dele.

O que você vai aprender

  • Criar um script de service worker básico, instalá-lo e fazer a depuração simples

O que você precisa saber

  • JavaScript e HTML básicos
  • Conceitos e sintaxe básica das promessas do ES2015
  • Como ativar o Play Console

O que é necessário antes de começar

Faça o download ou clone o repositório pwa-training-labs do GitHub e instale a versão LTS do Node.js, se necessário.

Navegue até o diretório service-worker-lab/app/ e inicie um servidor de desenvolvimento local:

cd service-worker-lab/app
npm install
node server.js

Você pode encerrar o servidor a qualquer momento usando o Ctrl-c.

Abra seu navegador e acesse localhost:8081/.

Observação: cancele o registro de todos os service workers e limpe todos os caches de service worker do localhost para que não interfiram no laboratório. No Chrome DevTools, você pode fazer isso clicando em Limpar dados do site na seção Limpar armazenamento da guia Aplicativo.

Abra a pasta service-worker-lab/app/ no editor de texto de sua preferência. A pasta app/ é onde você criará o laboratório.

Essa pasta contém:

  • below/another.html, js/another.js, js/other.js e other.html são exemplos de recursos que usamos para testar o escopo do service worker
  • styles/ pasta contém as folhas de estilo em cascata deste laboratório
  • test/ pasta contém arquivos para testar seu progresso.
  • index.html é a página HTML principal do nosso site/aplicativo de amostra
  • service-worker.js é o arquivo JavaScript usado para criar nosso service worker.
  • package.json e package-lock.json rastreiam os pacotes de nós usados neste projeto
  • O server.js é um servidor Express simples que usamos para hospedar nosso app.

Abra service-worker.js no seu editor de texto. O arquivo está vazio. Ainda não adicionamos nenhum código para ser executado no service worker.

Abra index.html no seu editor de texto.

Dentro das tags <script>, adicione o seguinte código para registrar o service worker:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('Service Worker is registered', registration);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Salve o script e atualize a página. O console retornará uma mensagem indicando que o service worker foi registrado. Para verificar se um service worker está registrado no Chrome, abra o DevTools (Control + Shift + I no Windows e Linux ou ⌘ + alt + I no Mac), clique na guia Application e depois na opção Service Workers. O resultado será semelhante ao seguinte:

Opcional: abra o site em um navegador incompatível e verifique se a condicional da verificação de suporte funciona.

Explicação

O código acima registra o arquivo service-worker.js como um service worker. Primeiro, ele verifica se o navegador é compatível com service workers. Isso deve ser feito sempre que você registrar um service worker, porque alguns navegadores podem não ser compatíveis com service workers. Em seguida, o código registra o service worker usando o método register da API ServiceWorkerContainer, que está contido na interface Navigator da janela.

navigator.serviceWorker.register(...) retorna uma promessa que é resolvida com um objeto registration quando o service worker é registrado. Se o registro falhar, a promessa será rejeitada.

As alterações no status do service worker são acionadas nos eventos dele.

Adicione listeners de eventos

Abra service-worker.js no seu editor de texto.

Adicione os seguintes listeners de eventos ao service worker:

self.addEventListener('install', event => {
  console.log('Service worker installing...');
  // Add a call to skipWaiting here
});

self.addEventListener('activate', event => {
  console.log('Service worker activating...');
});

Salve o arquivo.

Cancele manualmente o registro do service worker e atualize a página para instalar e ativar o service worker atualizado. O registro do console indica que o novo service worker foi registrado, instalado e ativado.

Observação: o registro de registro pode aparecer fora de ordem com os outros registros (instalação e ativação). O service worker é executado simultaneamente com a página, por isso não é possível garantir a ordem dos registros. O registro de registro vem da página, enquanto os registros de instalação e ativação vêm do service worker. No entanto, a instalação, a ativação e outros eventos do service worker ocorrem em uma ordem definida dentro do service worker, e precisam sempre aparecer na ordem esperada.

Explicação

O service worker emite um evento install no final do registro. No código acima, uma mensagem é registrada dentro do listener de eventos install, mas em um app real, esse seria um bom lugar para armazenar em cache recursos estáticos.

Quando um service worker é registrado, o navegador detecta se ele é novo (seja diferente do service worker instalado anteriormente ou porque não há um service worker registrado para esse site). Se o service worker for novo (como neste caso), ele será instalado pelo navegador.

O service worker emite um evento activate quando assume o controle da página. O código acima registra uma mensagem aqui, mas esse evento geralmente é usado para atualizar caches.

Somente um service worker pode estar ativo por vez em um determinado escopo (consulte "Como explorar o escopo do service worker"), portanto, um service worker recém-instalado não é ativado até que o service worker existente não esteja mais em uso. É por isso que todas as páginas controladas por um service worker precisam ser fechadas antes que um novo service worker possa assumir o controle. Como cancelamos o registro do service worker existente, o novo service worker foi ativado imediatamente.

Observação: a simples atualização da página não é suficiente para transferir o controle para um novo service worker, porque a nova página será solicitada antes do carregamento da página atual, e não haverá um momento em que o service worker antigo não esteja em uso.

Observação:você também pode ativar manualmente um novo service worker usando alguns navegadores e ferramentas de desenvolvedor e programaticamente com o skipWaiting(), que discutimos na seção 3.4.

Atualizar o service worker

Adicione o comentário a seguir em qualquer lugar do service-worker.js:

// I'm a new service worker

Salve o arquivo e atualize a página. Observe os registros no console. O novo service worker é instalado, mas não é ativado. No Chrome, você pode ver o service worker em espera na guia Application do DevTools.

Feche todas as páginas associadas ao service worker. Em seguida, abra a localhost:8081/ novamente. O registro do console indicará que o novo service worker foi ativado.

Observação: se você estiver recebendo resultados inesperados, verifique se o cache HTTP está desativado nas ferramentas para desenvolvedores.

Explicação

O navegador detecta uma diferença de byte entre o arquivo do service worker novo e o existente (devido ao comentário adicionado). Portanto, o novo service worker está instalado. Como apenas um service worker pode estar ativo por vez (para um determinado escopo), mesmo que o novo service worker esteja instalado, ele só é ativado quando o service worker existente não está mais em uso. Ao fechar todas as páginas com o controle do service worker antigo, podemos ativar o novo service worker.

Como pular a fase de espera

Um novo service worker pode ser ativado imediatamente, mesmo que um service worker existente esteja presente, pulando a fase de espera.

No service-worker.js, adicione uma chamada para skipWaiting no listener de eventos install:

self.skipWaiting();

Salve o arquivo e atualize a página. O novo service worker é instalado e ativado imediatamente, mesmo que um service worker anterior estivesse no controle.

Explicação

O método skipWaiting() permite que um service worker seja ativado assim que terminar a instalação. O listener de eventos de instalação é um lugar comum para colocar a chamada de skipWaiting(), mas ele pode ser chamado em qualquer lugar durante ou antes da fase de espera. Consulte esta documentação para saber mais sobre quando e como usar o skipWaiting(). No restante do laboratório, agora podemos testar o código do novo service worker sem cancelar o registro manualmente.

Mais informações

Os service workers podem atuar como um proxy entre seu app da Web e a rede.

Vamos adicionar um listener de busca para interceptar solicitações do nosso domínio.

Adicione o código a seguir a service-worker.js:

self.addEventListener('fetch', event => {
  console.log('Fetching:', event.request.url);
});

Salve o script e atualize a página para instalar e ativar o service worker atualizado.

Verifique o console e observe que nenhum evento de busca foi registrado. Atualize a página e verifique o console novamente. Você verá os eventos de busca para a página e os recursos dela, como o CSS.

Clique nos links para Outra página, Outra página e Voltar.

Você verá os eventos de busca no console para cada uma das páginas e os respectivos recursos. Todos os registros fazem sentido?

Observação:se você visitar uma página e o cache HTTP não estiver desativado, os recursos de CSS e JavaScript poderão ser armazenados em cache localmente. Se isso ocorrer, você não verá eventos de busca para esses recursos.

Explicação

O service worker recebe um evento de busca para cada solicitação HTTP feita pelo navegador que está no escopo. O objeto fetch event contém a solicitação. Detectar eventos de busca no service worker é semelhante a detectar eventos de cliques no DOM. No código, quando ocorre um evento de busca, registramos o URL solicitado no console. Na prática, também podemos criar e retornar nossa própria resposta personalizada com recursos arbitrários.

Por que nenhum evento de busca foi registrado na primeira atualização? Por padrão, os eventos de busca de uma página não passarão por um service worker a menos que a própria solicitação da página tenha passado por um. Isso garante consistência no site. Se uma página é carregada sem o service worker, o mesmo acontece com os sub-recursos.

Mais informações

Código da solução

Para uma cópia do código em funcionamento, navegue até a pasta 04-intercepting-network-requests/.

Os service workers têm escopo. O escopo do service worker determina de quais caminhos o service worker intercepta solicitações.

Encontrar o escopo

Atualize o código de registro em index.html com:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('SW registered with scope:', registration.scope);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Atualize o navegador. O console mostra o escopo do service worker (neste caso, o http://localhost:8081/).

Explicação

A promessa retornada por register() é resolvida no objeto de registro, que contém o escopo do service worker.

O escopo padrão é o caminho para o arquivo do service worker e se estende a todos os diretórios mais baixos. Portanto, um service worker no diretório raiz de um app controla as solicitações de todos os arquivos dele.

Mover o service worker

Mova service-worker.js para o diretório below/ e atualize o URL do service worker no código de registro em index.html.

Cancele o registro do service worker atual no navegador e atualize a página.

O console mostra que o escopo do service worker agora é http://localhost:8081/below/. No Chrome, você também pode ver o escopo do service worker na guia "Application" do DevTools:

Na página principal, clique em Outra página, Outra página e Voltar. Quais solicitações de busca estão sendo registradas? Quais não são?

Explicação

O escopo padrão do service worker é o caminho para o arquivo do service worker. Como o arquivo do service worker agora está no below/, esse é o escopo dele. Agora o console registra apenas eventos de busca para another.html, another.css e another.js, porque esses são os únicos recursos no escopo do service worker.

Definir um escopo arbitrário

Mova o service worker de volta para o diretório raiz do projeto (app/) e atualize o URL do service worker no código de registro em index.html.

Use a referência no MDN para definir o escopo do service worker para o diretório below/ usando o parâmetro opcional em register().

Cancele o registro do service worker e atualize a página. Clique em Outra página, Outra página e Voltar.

Novamente, o console mostra que o escopo do service worker agora é http://localhost:8081/below/ e registra eventos de busca apenas para another.html, another.css e another.js.

Explicação

É possível definir um escopo arbitrário transmitindo um parâmetro adicional ao fazer o registro. Por exemplo:

navigator.serviceWorker.register('/service-worker.js', {
  scope: '/kitten/'
});

No exemplo acima, o escopo do service worker está definido como /kitten/. O service worker intercepta solicitações de páginas em /kitten/ e /kitten/lower/, mas não de páginas como /kitten ou /.

Observação: não é possível definir um escopo arbitrário que esteja acima do local real do service worker. No entanto, se o service worker estiver ativo em um cliente veiculado com o cabeçalho Service-Worker-Allowed, será possível especificar um escopo máximo para esse service worker acima do local do service worker.

Mais informações

Código da solução

Para uma cópia do código em funcionamento, navegue até a pasta solution/.

Agora você tem um service worker simples em funcionamento e entende o ciclo de vida do service worker.

Mais informações

Ciclo de vida do service worker

Para ver todos os codelabs no curso de treinamento de PWA, consulte o codelab de boas-vindas do curso.