Crie um PWA com Webpack e Workbox
Publicados: 2022-03-10Um Progressive Web App (PWA) é um site que usa tecnologia moderna para oferecer experiências semelhantes a aplicativos na web. É um termo abrangente para novas tecnologias, como 'manifesto de aplicativo da Web', 'operário de serviço' e muito mais. Quando combinadas, essas tecnologias permitem que você forneça experiências de usuário rápidas e envolventes com seu site.
Este artigo é um tutorial passo a passo para adicionar um service worker a um site de uma página existente. O service worker permitirá que você faça seu site funcionar offline enquanto também notifica seus usuários sobre atualizações em seu site. Observe que isso é baseado em um pequeno projeto que acompanha o Webpack, portanto, usaremos o plug-in Workbox Webpack (Workbox v4).
Usar uma ferramenta para gerar seu service worker é a abordagem recomendada, pois permite gerenciar seu cache com eficiência. Usaremos o Workbox — um conjunto de bibliotecas que facilitam a geração do código do service worker — para gerar nosso service worker neste tutorial.
Dependendo do seu projeto, você pode usar o Workbox de três maneiras diferentes:
- Está disponível uma interface de linha de comando que permite integrar o workbox em qualquer aplicativo que você tenha;
- Está disponível um módulo Node.js que permite integrar a caixa de trabalho em qualquer ferramenta de compilação do Node, como gulp ou grunt;
- Um plug- in do webpack está disponível, permitindo que você integre facilmente a um projeto criado com o Webpack.
Webpack é um empacotador de módulos. Para simplificar, você pode pensar nisso como uma ferramenta que gerencia suas dependências de JavaScript. Ele permite que você importe código JavaScript de bibliotecas e agrupe seu JavaScript em um ou vários arquivos.
Para começar, clone o seguinte repositório em seu computador:
git clone [email protected]:jadjoubran/workbox-tutorial-v4.git cd workbox-tutorial-v4 npm install npm run dev
Em seguida, navegue até https://localhost:8080/
. Você deve conseguir ver o aplicativo de moeda que usaremos ao longo deste tutorial:
Comece com um shell de aplicativo
O shell do aplicativo (ou 'shell do aplicativo') é um padrão inspirado nos aplicativos nativos. Isso ajudará a dar ao seu aplicativo uma aparência mais nativa. Ele simplesmente fornece ao aplicativo um layout e estrutura sem nenhum dado - uma tela de transição que visa melhorar a experiência de carregamento do seu aplicativo da web.
Aqui estão alguns exemplos de shells de aplicativos de aplicativos nativos:
E aqui estão exemplos de shells de aplicativos de PWAs:
Os usuários gostam da experiência de carregamento de shells de aplicativos porque desprezam telas em branco. Uma tela em branco faz com que o usuário sinta que o site não está carregando. Isso os faz sentir como se o site estivesse travado.
Os shells de aplicativos tentam pintar a estrutura de navegação do aplicativo o mais rápido possível, como a barra de navegação, a barra de guias e um carregador que significa que o conteúdo solicitado está sendo carregado.
Então, como você constrói um shell de aplicativo?
O padrão de shell do aplicativo prioriza o carregamento do HTML, CSS e JavaScript que serão renderizados primeiro. Isso significa que precisamos dar prioridade total a esses recursos, portanto, você precisa inline esses ativos. Portanto, para criar um shell de aplicativo, basta inserir o HTML, CSS e JavaScript que são responsáveis pelo shell do aplicativo. Claro, você não deve alinhar tudo, mas sim ficar dentro de um limite de cerca de 30 a 40 KB no total.
Você pode ver o shell do aplicativo embutido no index.html . Você pode inspecionar o código-fonte verificando o arquivo index.html e visualizá-lo no navegador excluindo o elemento <main>
nas ferramentas de desenvolvimento:
Funciona off-line?
Vamos simular ficar offline! Abra o DevTools, navegue até a guia de rede e marque a caixa de seleção 'Offline'. Ao recarregar a página, você verá que obteremos a página offline do navegador.
Isso porque a solicitação inicial para /
(que carregará o arquivo index.html ) falhará porque a Internet está offline. A única maneira de recuperarmos dessa falha de solicitação é ter um service worker.
Vamos visualizar a solicitação sem um service worker:
Um service worker é um proxy de rede programável, o que significa que fica entre sua página da Web e a Internet. Isso permite controlar solicitações de rede de entrada e saída.
Isso é benéfico porque agora podemos redirecionar essa solicitação com falha para o cache (supondo que tenhamos o conteúdo no cache).
Um service worker também é um tipo de Web Worker, o que significa que ele é executado separadamente da página principal e não tem acesso à window
ou ao objeto de document
.
Precache o shell do aplicativo
Para fazer nosso aplicativo funcionar offline, vamos começar com o pré-cache do shell do aplicativo.
Então vamos começar instalando o plugin Webpack Workbox:
npm install --save-dev workbox-webpack-plugin
Então vamos abrir nosso arquivo index.js e registrar o service worker:
if ("serviceWorker" in navigator){ window.addEventListener("load", () => { navigator.serviceWorker.register("/sw.js"); }) }
Em seguida, abra o arquivo webpack.config.js e vamos configurar o plug-in Workbox webpack:
//add at the top const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); //add inside the plugins array: plugins: [ … , new WorkboxWebpackPlugin.InjectManifest({ swSrc: "./src/src-sw.js", swDest: "sw.js" }) ]
Isso instruirá o Workbox a usar nosso arquivo ./src/src-sw.js como base. O arquivo gerado será chamado sw.js e estará na pasta dist
.
Em seguida, crie um arquivo ./src/src-sw.js no nível raiz e escreva o seguinte dentro dele:
workbox.precaching.precacheAndRoute(self.__precacheManifest);
Nota : A variável self.__precacheManifest
será importada de um arquivo que será gerado dinamicamente pelo workbox.
Agora você está pronto para construir seu código com npm run build
e o Workbox irá gerar dois arquivos dentro da pasta dist
:
- precache-manifest.66cf63077c7e4a70ba741ee9e6a8da29.js
- sw.js
O sw.js importa a caixa de trabalho do CDN, bem como o manifesto precache.[chunkhash].js .
//precache-manifest.[chunkhash].js file self.__precacheManifest = (self.__precacheManifest || []).concat([ "revision": "ba8f7488757693a5a5b1e712ac29cc28", "url": "index.html" }, "url": "main.49467c51ac5e0cb2b58e.js" ]);
O manifesto do precache lista os nomes dos arquivos que foram processados pelo webpack e que acabam na sua pasta dist
. Usaremos esses arquivos para pré-armazená-los no navegador. Isso significa que, quando seu site for carregado pela primeira vez e registrar o service worker, ele armazenará esses ativos em cache para que possam ser usados na próxima vez.
Você também pode notar que algumas entradas têm uma 'revisão' enquanto outras não. Isso porque a revisão às vezes pode ser inferida do chunkhash do nome do arquivo. Por exemplo, vamos dar uma olhada no nome do arquivo main.49467c51ac5e0cb2b58e.js . Tem uma revisão no nome do arquivo, que é o chunkhash 49467c51ac5e0cb2b58e .
Isso permite que o Workbox entenda quando seus arquivos são alterados para que ele apenas limpe ou atualize os arquivos que foram alterados, em vez de despejar todo o cache toda vez que você publicar uma nova versão de seu service worker.
Na primeira vez que você carregar a página, o service worker será instalado. Você pode ver isso no DevTools. Primeiro, o arquivo sw.js é solicitado, que então solicita todos os outros arquivos. Eles estão claramente marcados com o ícone de engrenagem.
Assim, o Workbox inicializará e fará o pré-cache de todos os arquivos que estão no manifesto do precache. É importante verificar se você não tem arquivos desnecessários no arquivo de manifesto de precache, como arquivos .map ou arquivos que não fazem parte do shell do aplicativo.
Na guia rede, podemos ver as solicitações provenientes do service worker. E agora, se você tentar ficar offline, o shell do aplicativo já está em pré-cache, então funciona mesmo se estivermos offline!
Cache de Rotas Dinâmicas
Você notou que quando ficamos offline, o shell do aplicativo funciona, mas não nossos dados? Isso ocorre porque essas chamadas de API não fazem parte do shell do aplicativo pré -cache . Quando não houver conexão com a Internet, essas solicitações falharão e o usuário não poderá ver as informações de moeda.
No entanto, essas solicitações não podem ser armazenadas em pré-cache porque seu valor vem de uma API. Além disso, quando você começa a ter várias páginas, não deseja armazenar em cache todas as solicitações da API de uma só vez. Em vez disso, você deseja armazená-los em cache quando o usuário visitar essa página.
Chamamos isso de 'dados dinâmicos'. Eles geralmente incluem chamadas de API, bem como imagens e outros recursos solicitados quando um usuário realiza uma determinada ação em seu site (por exemplo, quando ele navega para uma nova página).
Você pode armazená-los em cache usando o módulo de roteamento do Workbox. Veja como:
//add in src/src-sw.js workbox.routing.registerRoute( /https:\/\/api\.exchangeratesapi\.io\/latest/, new workbox.strategies.NetworkFirst({ cacheName: "currencies", plugins: [ new workbox.expiration.Plugin({ maxAgeSeconds: 10 * 60 // 10 minutes }) ] }) );
Isso configurará o cache dinâmico para qualquer URL de solicitação que corresponda à URL https://api.exchangeratesapi.io/latest
.
A estratégia de armazenamento em cache que usamos aqui é chamada NetworkFirst
; existem dois outros que são frequentemente usados:
-
CacheFirst
-
StaleWhileRevalidate
CacheFirst
irá procurá-lo no cache primeiro. Se não for encontrado, ele o obterá da rede. StaleWhileRevalidate
irá para a rede e o cache ao mesmo tempo. Retorne a resposta do cache para a página (enquanto estiver em segundo plano) ele usará a nova resposta de rede para atualizar o cache para a próxima vez que for usado.
Para nosso caso de uso, tivemos que usar o NetworkFirst
porque estamos lidando com taxas de câmbio que mudam com muita frequência. No entanto, quando o usuário fica offline, podemos pelo menos mostrar as taxas como estavam há 10 minutos - é por isso que usamos o plug-in de expiração com maxAgeSeconds
definido como 10 * 60
segundos.
Gerenciar atualizações de aplicativos
Sempre que um usuário carregar sua página, o navegador executará o código navigator.serviceWorker.register
mesmo que o service worker já esteja instalado e em execução. Isso permite que o navegador detecte se há uma nova versão do service worker. Quando o navegador percebe que o arquivo não foi alterado, ele simplesmente ignora a chamada de registro. Depois que esse arquivo for alterado, o navegador entenderá que há uma nova versão do service worker e, portanto , instalará o novo service worker paralelamente ao service worker em execução no momento .
No entanto, ele pausa na fase installed/waiting
porque apenas um service worker pode ser ativado ao mesmo tempo.
Somente quando todas as janelas do navegador controladas pelo service worker anterior estiverem fechadas, torna-se seguro para o novo service worker ser ativado.
Você também pode controlar isso manualmente chamando skipWaiting()
(ou self.skipWaiting()
já que self
é o contexto de execução global no service worker). No entanto, na maioria das vezes você só deve fazer isso depois de perguntar ao usuário se ele deseja obter a atualização mais recente.
Felizmente, workbox-window
nos ajuda a conseguir isso. É uma nova biblioteca de janelas introduzida no Workbox v4 que visa simplificar tarefas comuns no lado da janela.
Vamos começar instalando com o seguinte:
npm install workbox-window
Em seguida, importe Workbox
na parte superior do arquivo index.js :
import { Workbox } from "workbox-window";
Em seguida, substituiremos nosso código de registro pelo seguinte:
if ("serviceWorker" in navigator) { window.addEventListener("load", () => { const wb = new Workbox("/sw.js"); wb.register(); }); }
Em seguida, encontraremos o botão de atualização que tem o IDworkbox-waiting
:
//add before the wb.register() const updateButton = document.querySelector("#app-update"); // Fires when the registered service worker has installed but is waiting to activate. wb.addEventListener("waiting", event => { updateButton.classList.add("show"); updateButton.addEventListener("click", () => { // Set up a listener that will reload the page as soon as the previously waiting service worker has taken control. wb.addEventListener("controlling", event => { window.location.reload(); }); // Send a message telling the service worker to skip waiting. // This will trigger the `controlling` event handler above. wb.messageSW({ type: "SKIP_WAITING" }); }); });
Este código mostrará o botão de atualização quando houver uma nova atualização (assim, quando o service worker estiver em estado de espera) e enviará uma mensagem SKIP_WAITING
ao service worker.
Precisaremos atualizar o arquivo do service worker e manipular o evento SKIP_WAITING
de forma que ele chame o skipWaiting
:
//add in src-sw.js addEventListener("message", event => { if (event.data && event.data.type === "SKIP_WAITING") { skipWaiting(); });
Agora execute npm run dev
e recarregue a página. Entre no seu código e atualize o título da barra de navegação para “Navbar v2”. Recarregue a página novamente e você poderá ver o ícone de atualização.
Empacotando
Nosso site agora funciona offline e é capaz de informar ao usuário sobre novas atualizações. No entanto, lembre-se de que o fator mais importante ao criar um PWA é a experiência do usuário. Sempre se concentre em criar experiências fáceis de usar por seus usuários. Nós, como desenvolvedores, tendemos a ficar muito empolgados com a tecnologia e muitas vezes acabamos esquecendo de nossos usuários.
Se quiser dar um passo adiante, você pode adicionar um manifesto de aplicativo da Web que permitirá que seus usuários adicionem o site à tela inicial. E se quiser saber mais sobre o Workbox, você pode encontrar a documentação oficial no site do Workbox.
Leitura adicional no SmashingMag:
- Você pode ganhar mais dinheiro com um aplicativo móvel ou um PWA?
- Um guia abrangente para aplicativos da Web progressivos
- Nativo e PWA: escolhas, não desafiadores!
- Construindo um PWA usando Angular 6