Service workers e o modelo de shell do aplicativo

Um recurso de arquitetura comum dos aplicativos da Web de página única (SPA, na sigla em inglês) é um conjunto mínimo de HTML, CSS e JavaScript necessário para potencializar a funcionalidade global de um aplicativo. Na prática, isso tende a ser o cabeçalho, a navegação e outros elementos comuns da interface do usuário que persistem em todas as páginas. Quando um service worker armazena previamente em cache o HTML e os recursos dependentes dessa interface, chamamos isso de shell do aplicativo.

Um diagrama de um shell do aplicativo. É uma captura de tela de uma página da Web com um cabeçalho na parte superior e uma área de conteúdo na parte inferior. O cabeçalho é rotulado como "Shell do aplicativo", enquanto o inferior é chamado "Conteúdo".

O shell do aplicativo desempenha um papel significativo no desempenho percebido de um aplicativo da Web. É a primeira coisa que carrega e, portanto, também é a primeira coisa que os usuários veem enquanto esperam o conteúdo preencher a interface do usuário.

Embora o shell do aplicativo seja de carregamento rápido (desde que a rede esteja disponível e pelo menos um pouco rápida), um service worker que pré-armazena em cache o shell do aplicativo e os recursos associados a ele oferece estes outros benefícios ao modelo de shell do aplicativo:

  • Performance confiável e consistente em visitas repetidas. Na primeira visita a um app sem um service worker instalado, a marcação do aplicativo e os recursos associados precisam ser carregados pela rede antes que o service worker possa colocá-los em cache. Entretanto, visitas repetidas extrairão o shell do aplicativo do cache, o que significa que o carregamento e a renderização serão instantâneos.
  • Acesso confiável a funcionalidades em cenários off-line. Às vezes, o acesso à Internet é irregular ou totalmente ausente, e a temida tela "não conseguimos encontrar esse site" aparece. O modelo de shell do aplicativo resolve isso respondendo a qualquer solicitação de navegação com a marcação de shell do aplicativo do cache. Mesmo que alguém visite um URL em seu aplicativo da Web que nunca tenha acessado antes, o shell do aplicativo será exibido a partir do cache e poderá ser preenchido com conteúdo útil.

Quando o modelo de shell do aplicativo precisa ser usado

Um shell do aplicativo é mais útil quando há elementos comuns da interface do usuário que não mudam de uma rota para outra, mas o conteúdo sim. A maioria dos SPAs provavelmente já usa um modelo de shell do aplicativo.

Se isso descreve seu projeto e você quiser adicionar um service worker para melhorar a confiabilidade e o desempenho, o shell do aplicativo precisa:

  • Carregar rapidamente.
  • Use recursos estáticos de uma instância de Cache.
  • Inclua elementos comuns da interface, como um cabeçalho e uma barra lateral, separados do conteúdo da página.
  • Recupere e exiba conteúdo específico da página.
  • Se apropriado, armazene em cache conteúdo dinâmico para visualização off-line.

O shell do aplicativo carrega o conteúdo específico da página de forma dinâmica por APIs ou conteúdo empacotado em JavaScript. Ela também deve ser de autoatualização no sentido de que, se a marcação do shell do aplicativo mudar, uma atualização do service worker deverá selecionar o novo shell do aplicativo e armazená-lo em cache automaticamente.

Como criar o shell do aplicativo

O shell do aplicativo deve existir independentemente do conteúdo e, ainda assim, fornecer uma base para o conteúdo ser preenchido dentro dele. O ideal é que ele seja o mais fino possível, mas inclua conteúdo significativo suficiente no download inicial para que o usuário entenda que uma experiência está sendo carregada rapidamente.

O equilíbrio certo depende do seu app. O shell do aplicativo do app Treinado para Thrill (link em inglês) de Jake Archibald inclui um cabeçalho com um botão de atualização para extrair novos conteúdos do Flickr.

Captura de tela do app da Web "Treinado para Thrill" em dois estados diferentes. À esquerda, apenas o shell do aplicativo armazenado em cache fica visível, sem conteúdo preenchido. À direita, o conteúdo (algumas imagens de alguns trens) é carregado dinamicamente na área de conteúdo do shell do aplicativo.

A marcação do shell do aplicativo varia de projeto para projeto. Veja um exemplo de arquivo index.html que fornece o código boilerplate do aplicativo:

​​<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Application Shell Example
    </title>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="styles/global.css">
  </head>
  <body>
    <header class="header">
      <!-- Application header -->
      <h1 class="header__title">Application Shell Example</h1>
    </header>

    <nav class="nav">
      <!-- Navigation items -->
    </nav>

    <main id="app">
      <!-- Where the application content populates -->
    </main>

    <div class="loader">
      <!-- Spinner/content placeholders -->
    </div>

    <!-- Critical application shell logic -->
    <script src="app.js"></script>

    <!-- Service worker registration script -->
    <script>
      if ('serviceWorker' in navigator) {
        // Register a service worker after the load event
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/sw.js');
        });
      }
    </script>
  </body>
</html>

Independentemente de como você cria um shell de aplicativo para seu projeto, ele precisa ter as seguintes características:

  • O HTML deve ter áreas claramente isoladas para elementos individuais da interface do usuário. No exemplo acima, isso inclui o cabeçalho, a navegação, a área de conteúdo principal e o espaço do aplicativo para um "ícone de carregamento" de carregamento que só aparece quando o conteúdo está sendo carregado.
  • O JavaScript e o CSS iniciais carregados para o shell do aplicativo devem ser mínimos e estar relacionados apenas à funcionalidade do próprio shell do aplicativo, não ao conteúdo. Isso garante que o aplicativo renderize o shell o mais rápido possível e minimiza o trabalho da linha de execução principal até que o conteúdo seja mostrado.
  • Script in-line que registra um service worker.

Depois que o shell do aplicativo for criado, será possível criar um service worker para armazenar em cache ele e os recursos.

Como armazenar o shell do aplicativo em cache

O shell do aplicativo e os recursos necessários são os itens que o service worker precisa armazenar em cache imediatamente no momento da instalação. Considerando um shell de aplicativo como o exemplo acima, vamos ver como fazer isso em um exemplo básico de caixa de trabalho usando workbox-build:

// build-sw.js
import {generateSW} from 'workbox-build';

// Where the generated service worker will be written to:
const swDest = './dist/sw.js';

generateSW({
  swDest,
  globDirectory: './dist',
  globPatterns: [
    // The necessary CSS and JS for the app shell
    '**/*.js',
    '**/*.css',
    // The app shell itself
    'shell.html'
  ],
  // All navigations for URLs not precached will use this HTML
  navigateFallback: 'shell.html'
}).then(({count, size}) => {
  console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});

Essa configuração armazenada em build-sw.js importa o CSS e o JavaScript do app, incluindo o arquivo de marcação do shell do aplicativo contido em shell.html. O script é executado com o Node da seguinte maneira:

node build-sw.js

O service worker gerado é gravado em ./dist/sw.js e registrará a seguinte mensagem quando terminar:

Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.

Quando a página é carregada, o service worker armazena em cache a marcação do shell do aplicativo e as dependências correspondentes:

Captura de tela do painel de rede no DevTools do Chrome mostrando uma lista de recursos transferidos por download da rede. Os recursos pré-armazenados em cache pelo service worker são diferenciados de outros recursos com uma engrenagem à esquerda na linha. Vários arquivos JavaScript e CSS são pré-armazenados em cache pelo service worker no momento da instalação.
O service worker armazena previamente em cache as dependências do shell do aplicativo no momento da instalação. As solicitações de pré-armazenamento em cache são as duas últimas linhas, e o ícone de engrenagem ao lado da solicitação indica que o service worker processou a solicitação.

O armazenamento em cache prévio do HTML, CSS e JavaScript do shell do seu aplicativo é possível em quase todos os fluxos de trabalho, incluindo projetos que usam bundlers. Conforme avança na documentação, você vai aprender a usar o Workbox diretamente para configurar o conjunto de ferramentas e criar um service worker que funcione melhor para seu projeto, seja ele SPA ou não.

Conclusão

Combinar o modelo de shell do aplicativo com um service worker é ótimo para armazenamento em cache off-line, principalmente se você combinar a funcionalidade de pré-armazenamento em cache com uma estratégia que prioriza a rede e volta à estratégia de cache para marcação ou respostas de API. O resultado é uma experiência rápida e confiável que renderiza instantaneamente o shell do seu aplicativo em acessos repetidos, mesmo off-line.