Como aplicar os princípios de programação do miniapp a um projeto de exemplo

O domínio do aplicativo

Para mostrar como programar mini apps aplicado a um aplicativo da web, eu precisava de uma ideia de aplicativo pequena, mas completa. Treinamento intervalado de alta intensidade (HIIT) é uma estratégia de exercício cardiovascular de conjuntos alternados de curtos períodos de exercício anaeróbico intenso com períodos de recuperação menos intensos. Muitos treinamentos de HIIT usam timers de HIIT, por exemplo, esta sessão online de 30 minutos do canal The Body Coach TV no YouTube.

Sessão on-line de treinamento HIIT com timer verde de alta intensidade.
Período ativo.
.
Sessão on-line de HIIT com timer vermelho de baixa intensidade.
Período de descanso.

App de exemplo de HIIT Time

Para este capítulo, criei um exemplo básico de um aplicativo de cronômetro HIIT chamado "Hora de HIIT" que permite ao usuário definir e gerenciar vários timers, sempre consistindo em um intervalo de intensidade alta e baixa, e depois selecione um deles para uma sessão de treinamento. É um aplicativo responsivo com uma barra de navegação, uma barra de guias e três páginas:

  • Treino:é a página ativa durante um treino. Permite que o usuário selecione um dos timers e apresenta três anéis de progresso: o número de séries, o período ativo e o período de descanso.
  • Timers:gerencia os timers atuais e permite que o usuário crie novos.
  • Preferências:permite alternar os efeitos sonoros e a saída de voz e selecionar o idioma e o tema.

As capturas de tela a seguir mostram uma impressão do aplicativo.

App de exemplo de HIIT Time no modo retrato.
Hora do HIIT: treino no modo retrato.
.
App de exemplo de HIIT Time no modo paisagem.
Hora do HIIT: treino no modo paisagem.
.
App de exemplo de HIIT Time mostrando o gerenciamento de um timer.
Gerenciamento do timer de HIIT.

Estrutura do app

Conforme descrito acima, o aplicativo consiste em uma barra de navegação, uma barra de guias e três páginas, organizadas em uma grade. A Navbar e a Tabbar são concretizadas como iframes com um contêiner <div> entre eles, com mais três iframes para as páginas, sendo que uma delas está sempre visível e depende da seleção ativa na barra de guias. Um iframe final que aponta para about:blank é veiculado para páginas no app criadas dinamicamente, que são necessárias para modificar ou criar novos. Eu chamo esse padrão de app de página única de várias páginas (MPSPA, na sigla em inglês).

Visualização da estrutura HTML do app no Chrome DevTools, mostrando que ele consiste em seis iframes: um para a barra de navegação, um para a barra de guias e três agrupados para cada página do app, com um iframe de marcador de posição final para páginas dinâmicas.
O app consiste em seis iframes.

Marcação lit-html baseada em componentes

A estrutura de cada página é realizada como um scaffold do lit-html que é avaliado dinamicamente no ambiente de execução. Para informações sobre lit-html, é uma biblioteca de modelos HTML eficiente, expressiva e extensível para JavaScript. Ao usá-lo diretamente nos arquivos HTML, o modelo de programação mental é diretamente orientado para a saída. Como programador, você escreve um modelo de como será o resultado final, e o lit-html preenche as lacunas de forma dinâmica com base nos seus dados e conecta os listeners de eventos. O app usa elementos personalizados de terceiros, como o <sl-progress-ring> do Shoelace, ou um elemento personalizado autoimplementado chamado <human-duration>. Como os elementos personalizados têm uma API declarativa (por exemplo, o atributo percentage do anel de progresso), elas funcionam bem com lit-html, como é possível ver na lista abaixo.

<div>
  <button class="start" @click="${eventHandlers.start}" type="button">
    ${strings.START}
  </button>
  <button class="pause" @click="${eventHandlers.pause}" type="button">
    ${strings.PAUSE}
  </button>
  <button class="reset" @click="${eventHandlers.reset}" type="button">
    ${strings.RESET}
  </button>
</div>

<div class="progress-rings">
  <sl-progress-ring
    class="sets"
    percentage="${Math.floor(data.sets/data.activeTimer.sets*100)}"
  >
    <div class="progress-ring-caption">
      <span>${strings.SETS}</span>
      <span>${data.sets}</span>
    </div>
  </sl-progress-ring>
</div>
Três botões e um anel de progresso.
Seção renderizada da página correspondente à marcação acima.

Modelo de programação

Cada página tem uma classe Page correspondente que preenche a marcação lit-html com vida ao fornecer implementações dos manipuladores de eventos e fornecer os dados para cada página. Essa classe também oferece suporte a métodos de ciclo de vida, como onShow(), onHide(), onLoad() e onUnload(). As páginas têm acesso a um repositório de dados que serve para compartilhar o estado por página e o estado global com opção de persistência. Todas as strings são gerenciadas centralmente, portanto, a internacionalização está integrada. O roteamento é tratado pelo navegador essencialmente sem custo financeiro, uma vez que tudo o que o aplicativo faz é alternar a visibilidade do iframe e Para páginas criadas dinamicamente, altere o atributo src do iframe do marcador de posição. O exemplo abaixo mostra o código para fechar uma página criada dinamicamente.

import Page from '../page.js';

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
Página no app percebida como um iframe.
A navegação acontece do iframe para o iframe.

Estilo

O estilo das páginas ocorre por página, em seu próprio arquivo CSS com escopo. Isso significa que os elementos geralmente podem ser abordados diretamente pelos nomes, já que não pode ocorrer conflito com outras páginas. Estilos globais são adicionados a cada página. Portanto, configurações centrais, como font-family ou box-sizing não precisam ser declaradas repetidamente. É aqui que as opções de temas e modo escuro são definidas. A lista abaixo mostra as regras para a página Preferências que estabelecem os vários elementos de formulário em uma grade.

main {
  max-width: 600px;
}

form {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.5rem;
  margin-block-end: 1rem;
}

label {
  text-align: end;
  grid-column: 1 / 2;
}

input,
select {
  grid-column: 2 / 3;
}
Página de preferências do app HIIT Time mostrando um formulário no layout de grade.
Cada página é o seu próprio mundo. O estilo acontece diretamente com os nomes dos elementos.

Wake lock de tela

A tela não deve apagar durante os treinos. Em navegadores compatíveis, o HIIT Time detecta isso com um wake lock de tela. O snippet abaixo mostra como isso é feito.

if ('wakeLock' in navigator) {
  const requestWakeLock = async () => {
    try {
      page.shared.wakeLock = await navigator.wakeLock.request('screen');
      page.shared.wakeLock.addEventListener('release', () => {
        // Nothing.
      });
    } catch (err) {
      console.error(`${err.name}, ${err.message}`);
    }
  };
  // Request a screen wake lock…
  await requestWakeLock();
  // …and re-request it when the page becomes visible.
  document.addEventListener('visibilitychange', async () => {
    if (
      page.shared.wakeLock !== null &&
      document.visibilityState === 'visible'
    ) {
      await requestWakeLock();
    }
  });
}

Teste o aplicativo

O aplicativo HIIT Time está disponível no GitHub. Abra a demonstração em uma nova janela, ou diretamente no iframe incorporado abaixo, que simula um dispositivo móvel.

Agradecimentos

Este artigo foi revisado por João Medley, Bascos Kayce, Milica Mihajlija, Alan Kent, e Keith Gu.