Service workers e o modelo de shell do aplicativo

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

Um diagrama de um shell de 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 é chamado de "Application Shell", e a parte de baixo é "Conteúdo".

O shell do aplicativo desempenha um papel significativo na percepção de desempenho de um aplicativo da Web. Ela é a primeira coisa que é carregada e, portanto, também é a primeira coisa que os usuários veem enquanto esperam o conteúdo preencher a interface.

Embora o shell do aplicativo seja rápido de carregar (desde que a rede esteja disponível e pelo menos um pouco rápida), um service worker que armazena previamente em cache o shell do aplicativo e os recursos associados dá ao modelo do shell do aplicativo estes benefícios adicionais:

  • 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 seu cache. No entanto, 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 à funcionalidade em cenários off-line. Às vezes, o acesso à Internet é irregular, ou totalmente ausente, e o temido "não conseguimos encontrar o site" tela vira a cabeça. O modelo de shell do aplicativo resolve isso respondendo a qualquer solicitação de navegação com a marcação do shell do aplicativo do cache. Mesmo que alguém visite um URL em seu aplicativo da web pela qual nunca esteve antes, o shell do aplicativo será disponibilizado a partir do cache e poderá ser preenchido com conteúdo útil.

Quando o modelo de shell do aplicativo precisa ser usado

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

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

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

O shell do aplicativo carrega conteúdo específico da página dinamicamente por meio de APIs ou de conteúdos empacotados em JavaScript. Ele também precisa ser autoatualizado, no sentido de que, se a marcação do shell do aplicativo mudar, uma atualização do service worker deve pegar o novo shell do aplicativo e armazená-lo em cache automaticamente.

Criar o shell do aplicativo

O shell do aplicativo precisa existir independentemente do conteúdo, mas 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 ideal depende do seu app. O shell do aplicativo Trained To Thrill de Jake Archibald inclui um cabeçalho com um botão de atualização para extrair um novo conteúdo do Flickr.

Uma captura de tela do app da Web treinado para entusiasmar 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 fotos 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, mas este é um exemplo de um 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ê constrói 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 do aplicativo, a navegação, a área de conteúdo principal e o espaço para um ícone de carregamento que só aparece quando o conteúdo está carregando.
  • O JavaScript e o CSS iniciais carregados para o shell do aplicativo devem ser mínimos e relacionados apenas à funcionalidade do shell do aplicativo, e 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 apareça.
  • Um script in-line que registra um service worker.

Depois de criar o shell do aplicativo, você pode criar um service worker para armazenar em cache tanto ele quanto seus ativos.

Como armazenar o shell do aplicativo em cache

O shell do aplicativo e os recursos necessários são o que o service worker deve pré-armazenar em cache imediatamente no momento da instalação. Considerando um shell do aplicativo como o exemplo acima, vamos conferir 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 de shell do aplicativo contido em shell.html. O script é executado com Node da seguinte forma:

node build-sw.js

O service worker gerado é gravado em ./dist/sw.js e vai registrar a seguinte mensagem quando concluído:

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 dela:

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 da 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 tratou a solicitação.

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

Conclusão

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