Aplicar los principios de programación de miniapps a un proyecto de ejemplo

El dominio de la app

Para mostrar la forma de programación de miniapps que se aplica a una app web, necesitaba una idea pequeña, pero lo suficientemente completa. El entrenamiento por intervalos de alta intensidad (HIIT) es una estrategia de ejercicio cardiovascular que consiste en sesiones alternas de períodos cortos de ejercicio anaeróbico intenso con períodos de recuperación menos intensos. Muchas sesiones de entrenamiento HIIT usan temporizadores, por ejemplo, esta sesión en línea de 30 minutos del canal de YouTube The Body Coach TV.

Sesión en línea de entrenamiento HIIT con temporizador verde de alta intensidad.
Período de activación.
Sesión en línea de entrenamiento HIIT con temporizador rojo de intensidad baja.
Período de descanso.

App de ejemplo de HIIT Time

En este capítulo, compilé un ejemplo básico de una aplicación de temporizador HIIT llamada acertadamente "Tiempo HIIT" que permite al usuario definir y administrar varios temporizadores, que siempre consisten en un intervalo de intensidad alta y baja, y, luego, seleccionar uno para una sesión de entrenamiento. Es una app responsiva con una barra de navegación, una barra de pestañas y tres páginas:

  • Entrenamiento: Es la página activa durante un entrenamiento. Permite que el usuario seleccione uno de los cronómetros y cuenta con tres tonos de progreso: la cantidad de conjuntos, el período activo y el período de descanso.
  • Temporizadores: Administra los temporizadores existentes y permite que el usuario cree otros nuevos.
  • Preferencias: Te permite activar o desactivar los efectos de sonido y la salida de voz, y seleccionar el idioma y el tema.

Las siguientes capturas de pantalla dan una impresión de la aplicación.

App de ejemplo de HIIT Time en modo vertical
Pestaña "Entrenamiento" de HIIT Time en modo vertical
App de ejemplo de HIIT Time en modo horizontal
Pestaña "Entrenamiento" de HIIT Time en modo horizontal
App de ejemplo de HIIT Time que muestra la administración de un cronómetro
Administración del cronómetro de tiempo HIIT.

Estructura de app

Como se describió anteriormente, la app consta de una barra de navegación, una barra de pestañas y tres páginas organizadas en una cuadrícula. La barra de navegación y la barra de pestañas se realizan como iframes con un contenedor <div> entre ellos, con tres iframes más para las páginas, de los cuales uno siempre está visible y depende de la selección activa en la barra de pestañas. Se publica un iframe final que apunta a about:blank para las páginas dentro de la app creadas de forma dinámica, que son necesarios para modificar los cronómetros existentes o crear nuevos. A este patrón lo llamo app de una sola página (MPSPA).

Vista de las Herramientas para desarrolladores de Chrome sobre la estructura HTML de la app, que muestra que consta de seis iframes: uno para la barra de navegación, uno para la barra de pestañas y tres agrupados para cada página de la app, con un iframe de marcador de posición final para páginas dinámicas.
La app consta de seis iframes.

Lenguaje de marcado lit-html basado en componentes

La estructura de cada página se realiza como un andamiaje lit-html que se evalúa de forma dinámica durante el tiempo de ejecución. Para obtener un fondo en lit-html, se trata de una biblioteca de plantillas HTML eficiente, expresiva y extensible para JavaScript. Al usarlo directamente en los archivos HTML, el modelo de programación mental está directamente orientado a los resultados. Como programador, escribes una plantilla de cómo se verá el resultado final, y con lit-html, se llenan los vacíos de forma dinámica según tus datos y se conectan los objetos de escucha de eventos. La app usa elementos personalizados de terceros, como <sl-progress-ring> de Shoelace, o un elemento personalizado autoimplementado llamado <human-duration>. Dado que los elementos personalizados tienen una API declarativa (por ejemplo, el atributo percentage del anillo de progreso), funcionan bien con lit-html, como se puede ver en la siguiente lista.

<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>
Tres botones y un anillo de progreso.
Sección renderizada de la página correspondiente al lenguaje de marcado anterior.

Modelo de programación

Cada página tiene una clase Page correspondiente que rellena el lenguaje de marcado lit-html con vida proporcionando implementaciones de los controladores de eventos y los datos para cada página. Esta clase también admite métodos de ciclo de vida como onShow(), onHide(), onLoad() y onUnload(). Las páginas tienen acceso a un almacén de datos que sirve para compartir el estado por página y el estado global persistentes opcionalmente. Todas las cadenas se administran de forma centralizada, por lo que la internacionalización está integrada. El navegador controla el enrutamiento de forma gratuita, ya que lo único que hace la app es activar o desactivar la visibilidad del iframe y, para las páginas creadas de forma dinámica, cambia el atributo src del iframe del marcador de posición. En el siguiente ejemplo, se muestra el código para cerrar una página creada de forma dinámica.

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

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
La página en la app se materializó como un iframe.
La navegación se realiza del iframe al iframe.

Diseño

El diseño de las páginas se realiza por página en su propio archivo CSS con alcance. Esto significa que los elementos suelen abordarse directamente con sus nombres de elementos, ya que no se pueden producir conflictos con otras páginas. Los diseños globales se agregan a cada página, por lo que no es necesario declarar varias veces la configuración central, como font-family o box-sizing. Aquí también se definen las opciones de temas y modo oscuro. En la siguiente lista, se muestran las reglas de la página Preferencias que define los distintos elementos del formulario en una cuadrícula.

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 preferencias de la app de HIIT Time que muestra un formulario con un diseño de cuadrícula.
Cada página es su propio mundo. El diseño se realiza directamente con los nombres de los elementos.

Bloqueo de activación de pantalla

La pantalla no debería apagarse durante un entrenamiento. En los navegadores compatibles, HIIT Time lo detecta a través de un bloqueo de activación de pantalla. En el siguiente fragmento, se muestra cómo hacerlo.

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();
    }
  });
}

Cómo probar la aplicación

La aplicación de HIIT Time está disponible en GitHub. Puedes jugar con la demostración en una ventana nueva o directamente en la incorporación de iframe a continuación, que simula un dispositivo móvil.

Agradecimientos

Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent y Keith Gu revisaron este artículo.