Back

Generación Automática de Skeleton Screens con boneyard

Generación Automática de Skeleton Screens con boneyard

Los skeleton loaders son uno de esos patrones de UI que parecen simples hasta que realmente los implementas. Mides elementos del DOM, codificas alturas a mano, configuras renderizado condicional—y luego tu diseñador cambia el diseño de la tarjeta y tienes que hacerlo todo de nuevo. El costo de mantenimiento se acumula silenciosamente.

boneyard-js adopta un enfoque diferente: en lugar de escribir la UI del skeleton a mano, extrae los datos del layout directamente desde tus componentes renderizados en tiempo de desarrollo y genera las definiciones de los placeholders automáticamente.

Puntos Clave

  • boneyard-js genera skeleton loaders automáticamente capturando datos del layout desde tus componentes reales, eliminando la necesidad de mantener dos representaciones paralelas de la misma UI.
  • El escaneo del DOM ocurre en tiempo de desarrollo a través de Playwright, produciendo archivos .bones.json estáticos. No se realiza ningún recorrido del DOM en tiempo de ejecución en producción.
  • La captura se ejecuta en tres anchos de viewport por defecto (375, 768, 1280px), con los valores horizontales almacenados como porcentajes para un comportamiento responsive.
  • La prop fixture y el flag --wait manejan componentes que dependen de datos asíncronos no disponibles durante la captura.
  • Un plugin de Vite mantiene los bones sincronizados automáticamente en cada actualización HMR, eliminando la necesidad de un paso CLI separado.
  • Existen adaptadores para React, Vue, Svelte 5, Angular, Preact y React Native, todos compartiendo el mismo formato base.

El Problema con los Skeleton Loaders Manuales

La mayoría de las implementaciones de skeleton loaders están desconectadas de la UI real. Construyes el componente real y luego, por separado, construyes un skeleton que aproxima su forma. Cuando el componente cambia, el skeleton se desincroniza. Con el tiempo, tus estados de carga dejan de coincidir con el contenido, causando desplazamientos de layout e inconsistencia visual.

Librerías como react-loading-skeleton reducen el código repetitivo, pero aún así requieren que describas manualmente la estructura. Sigues manteniendo dos representaciones del mismo componente.

Cómo boneyard-js Genera Skeleton Screens Automáticamente

boneyard-js invierte el flujo de trabajo. Envuelves tu componente real en una etiqueta <Skeleton>, ejecutas un comando CLI, y la herramienta captura el layout por ti.

Así se ve en React:

import { Skeleton } from 'boneyard-js/react'

function ActivityPanel() {
  const { data, isLoading } = useFetch('/api/activity')

  return (
    <Skeleton name="activity" loading={isLoading}>
      {data && <ActivityContent data={data} />}
    </Skeleton>
  )
}

Luego, con tu servidor de desarrollo en ejecución:

npx boneyard-js build

El CLI abre un navegador headless mediante Playwright, visita tu aplicación, encuentra cada elemento <Skeleton name="..."> y recorre el árbol del DOM dentro de él. Utiliza getBoundingClientRect() en los nodos hoja—elementos de texto, imágenes, botones, campos de formulario—y registra su posición y tamaño relativos al root del skeleton. Los valores horizontales se almacenan como porcentajes para un comportamiento responsive, mientras que los valores verticales se almacenan en píxeles. El border radius se detecta automáticamente.

Este proceso se ejecuta en tres anchos de viewport por defecto (375, 768, 1280px), produciendo un archivo .bones.json por componente:

{
  "breakpoints": {
    "375": {
      "bones": [
        { "x": 0, "y": 32, "w": 43.59, "h": 34, "r": 8 },
        { "x": 43.59, "y": 39, "w": 23.76, "h": 33, "r": 999 }
      ]
    }
  }
}

También se genera un archivo de registro (registry.js o registry.ts). Impórtalo una vez en el punto de entrada de tu aplicación y todas las definiciones de skeletons quedarán disponibles globalmente:

import './bones/registry'

En tiempo de ejecución, el componente <Skeleton> lee los datos de bones registrados para su name, renderiza el layout correspondiente como rectángulos posicionados de forma absoluta y los reemplaza por contenido real cuando loading se vuelve false.

Captura en Tiempo de Build, No Escaneo en Tiempo de Ejecución

Una distinción importante: el escaneo del DOM ocurre durante el desarrollo, no en producción. Los archivos .bones.json son artefactos estáticos que se commitean junto al código fuente. En tiempo de ejecución, boneyard-js solo lee esas definiciones pregeneradas. No hay recorrido del DOM en vivo en el navegador.

Para React Native, el mecanismo de captura es diferente. El componente <Skeleton> escanea el fiber tree en modo dev usando UIManager, mide las vistas nativas y envía esos datos al CLI. En builds de producción, este código de escaneo se excluye por completo.

Manejo de Contenido Dinámico y la Prop fixture

Si tu componente depende de una API que no está disponible durante la captura del CLI, el skeleton puede generarse incorrectamente porque el área de contenido está vacía. Dos opciones resuelven esto:

Usar --wait para retrasar la captura después de cargar la página:

npx boneyard-js build --wait 2000

Usar la prop fixture para proporcionar datos mock estáticos específicamente para la captura:

<Skeleton
  name="activity"
  loading={isLoading}
  fixture={<ActivityContent data={mockData} />}
>
  {data && <ActivityContent data={data} />}
</Skeleton>

El contenido de fixture solo se renderiza durante la captura del CLI y no tiene ningún efecto en producción.

Atención: Si tu componente no renderiza nada mientras data es undefined, el elemento contenedor colapsa a altura cero y el skeleton no se mostrará. Establece un minHeight en el <Skeleton> para evitar esto.

Plugin de Vite para una Integración Más Estrecha

Para proyectos basados en Vite, puedes prescindir por completo de la terminal CLI separada:

// vite.config.ts
import { defineConfig } from 'vite'
import { boneyardPlugin } from 'boneyard-js/vite'

export default defineConfig({
  plugins: [boneyardPlugin()]
})

Los bones se capturan cuando inicia el servidor de desarrollo y se vuelven a capturar automáticamente en cada actualización HMR. Esto mantiene tus skeleton loaders generados en build sincronizados con tu UI sin ninguna intervención manual.

Soporte de Frameworks

boneyard-js distribuye adaptadores específicos por framework como exports separados del paquete:

FrameworkImport
Reactboneyard-js/react
Vueboneyard-js/vue
Svelte 5boneyard-js/svelte
Angularboneyard-js/angular
Preactboneyard-js/preact
React Nativeboneyard-js/native

La lógica de extracción central y el formato .bones.json se comparten entre todos ellos.

¿Vale la Pena Adoptar boneyard-js?

boneyard-js es una herramienta relativamente nueva, así que es de esperar que la API evolucione. Dicho esto, el concepto central es sólido: generar la UI del skeleton a partir de datos de layout reales en lugar de mantenerla a mano.

El beneficio práctico es más evidente en proyectos donde los componentes cambian con frecuencia. En lugar de actualizar los placeholders del skeleton después de cada iteración de diseño, vuelves a ejecutar un comando. Los archivos .bones.json se actualizan, y los estados de carga del frontend se mantienen precisos.

Si ya estás invirtiendo tiempo en mantener los skeleton loaders sincronizados con tu UI real, la automatización vale el costo de configuración.

Conclusión

Los skeleton loaders no deberían desincronizarse de los componentes que representan, sin embargo, las implementaciones manuales casi siempre lo hacen. boneyard-js aborda esto tratando los skeletons como un artefacto generado en lugar de uno escrito a mano. El paso de captura se ejecuta en dev, la salida es un archivo JSON estático, y el costo en tiempo de ejecución es mínimo. Para equipos que iteran rápidamente sobre la UI, ese flujo de trabajo ahorra tiempo real y mantiene los estados de carga visualmente fieles a los componentes subyacentes.

Preguntas Frecuentes

No ocurre ningún escaneo del DOM en producción. El CLI o el plugin de Vite genera archivos .bones.json estáticos en tiempo de desarrollo. En producción, el componente Skeleton lee esas definiciones y renderiza rectángulos posicionados de forma absoluta, añadiendo solo un pequeño costo en tiempo de ejecución.

Captura los bones en tres anchos de viewport por defecto: 375, 768 y 1280 píxeles. Las posiciones y anchos horizontales se almacenan como porcentajes para que el skeleton escale fluidamente entre breakpoints, mientras que los valores verticales se mantienen en píxeles para un espaciado predecible. En tiempo de ejecución, se selecciona el breakpoint más cercano según el ancho de viewport actual.

Tienes dos opciones. El flag --wait retrasa la captura después de cargar la página para dar tiempo a que las solicitudes asíncronas se resuelvan. Alternativamente, la prop fixture acepta una versión mock de tu componente con datos estáticos, que se renderiza solo durante la captura del CLI. Ambos enfoques aseguran que el skeleton refleje un layout poblado en lugar de un contenedor vacío.

Sí, y deberías hacerlo. Los archivos .bones.json y registry.js son artefactos estáticos que describen los layouts de tus skeletons. Commitearlos mantiene a tu equipo alineado, hace que los builds sean reproducibles sin necesidad de ejecutar el paso de captura en CI, y permite que la revisión de código detecte cambios inesperados de layout junto con las ediciones de componente que los causaron.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay