Extraer HTML Estático
Extrae un archivo HTML estático autocontenido de cualquier aplicación web.
Qué Estrategia Usar
DEBES preguntar al usuario qué estrategia desea usar antes de proceder. Presenta las opciones claramente, recomienda la Estrategia A como la opción predeterminada preferida, y proporciona un breve resumen de pros/contras para cada opción para ayudarlos a tomar una decisión informada.
| Estrategia A (Puppeteer) | Estrategia B (Subagente de Navegador) | |
|---|---|---|
| Cuándo | La aplicación se ejecuta localmente, sin muro de autenticación | Necesitas interactuar primero con la página (hacer clic, llenar formularios) |
| Fidelidad | La más alta — estilos computados resueltos | Alta — DOM renderizado |
| Configuración | Cero — no se necesita mock | Cero — no se necesita mock |
| Framework | Cualquiera | Cualquiera |
| Salida | Escribe a archivo — sin límite de tamaño | Puede truncarse en el contexto del agente |
[!WARNING] Punto de Control — Se Requiere Confirmación del Usuario. DEBES preguntar al usuario qué estrategia prefiere antes de proceder. Presenta la tabla comparativa anterior, recomienda la Estrategia A como la predeterminada, y espera la aprobación explícita. NO tomes la decisión tú mismo ni continúes hasta que el usuario confirme.
Estrategia A: Instantánea con Puppeteer (Recomendada)
Lanza Chrome headless, captura el DOM completamente renderizado y produce un archivo HTML autocontenido con todo el CSS inline y las imágenes como base64. Funciona con cualquier framework — no se necesita MockPage.jsx.
Prerrequisitos
- Aplicación ejecutándose localmente (ej.,
npm run dev) - Node.js con
puppeteerdisponible (verifica:node -e "require('puppeteer')")
Flujo de Trabajo
-
Inicia la Aplicación y anota el puerto.
[!WARNING] Punto de Control — Se Requiere Confirmación del Usuario. Después de iniciar el servidor local, DEBES pausar y pedir al usuario confirmación antes de ejecutar el script de instantánea o lanzar un subagente de navegador. Reporta la URL y el puerto al usuario para que pueda verificar que la aplicación se está ejecutando y renderizando correctamente. NO continúes con el paso de instantánea hasta que el usuario confirme.
-
Ejecuta el Script de Instantánea:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000 -
Múltiples páginas — ejecuta una vez por ruta:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 --output .stitch/home.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/pricing --output .stitch/pricing.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/dashboard --output .stitch/dashboard.html --wait 2000 --html-class dark -
Limpia el Servidor de Desarrollo: Si se inició un servidor de desarrollo local específicamente para la extracción de instantáneas, asegúrate de detener el proceso del servidor o finalizar la tarea en segundo plano una vez que se complete la extracción.
Banderas del Script
| Bandera | Predeterminado | Descripción |
|---|---|---|
--url | (requerido) | URL a capturar |
--output | (requerido) | Ruta del archivo de salida |
--wait | 1000 | Espera adicional (ms) después de que la red esté inactiva. Aumenta para aplicaciones con carga diferida. |
--viewport | 1280x800 | Tamaño del viewport como ANCHOxALTO |
--html-class | — | Clase(s) para el elemento <html> (ej., dark) |
--remove-fixed | false | Elimina elementos fijos/pegajosos (banners de cookies, widgets de chat) |
--full-height | false | Redimensiona el viewport a la altura total del scroll |
--title | — | Sobrescribe el título de la página (establécelo en la ruta, ej., /dashboard o /settings/profile) |
--auth-script | — | Ruta a un módulo JS/TS que exporta una función por defecto async (page) => void para autenticación |
--inline-canvas | false | Convierte elementos <canvas> (ECharts, Chart.js, D3) a etiquetas <img> base64 |
Qué Hace Automáticamente
- Captura todas las reglas CSSOM de
document.styleSheets(preserva estilos dinámicos de desarrollo de Vite/Tailwind y CSS-in-JS) - Inlinea todos los
<link rel="stylesheet">→ bloques<style> - Convierte
<img>srcysrcset→ URIs de datos base64 (omite fuentes externas) - Inlinea archivos de fuentes de icono del mismo origen y relativos (
@font-face) como URIs de datos base64 para que las ligaduras nunca se rendericen como texto ASCII - Inlinea URLs de
<source srcset>como base64 - Elimina entradas
srcsetfallidas/muertas para que el navegador recurra alsrcinlineado - Elimina etiquetas
<script>, bloques de estilo de desarrollo de Vite HMR (createHotContext,import.meta.hot) y superposiciones de desarrollo - Resuelve rutas relativas de CSS
url()antes de inlinear
Notas por Framework
| Framework | Notas |
|---|---|
| React + Vite | Funciona de inmediato. --wait 1000. |
| Next.js | --wait 3000 para hidratación SSR. URL: http://localhost:3000. <img srcset> de /_next/image se inlinea automáticamente como base64. |
| Angular (@angular/cli / v17+) | Funciona de inmediato con ng serve (URL predeterminada: http://localhost:4200). --wait 2000 para hidratación de animaciones de Angular Material / PrimeNG y rutas con carga diferida. |
| Vue / Nuxt | Funciona de inmediato. |
| Svelte / SvelteKit | Funciona de inmediato. |
| Storybook | Usa la URL de la historia: --url http://localhost:6006/?path=/story/... |
| SSR (Webpack) | Puede necesitar un --wait más largo. |
Solución de Problemas
| Problema | Solución |
|---|---|
| Faltan imágenes | Aumenta --wait |
| Las imágenes se muestran rotas después de que el servidor se detiene | Verifica que srcset se haya inlineado — busca en el registro "Inlined N images". Si las URLs de srcset fallaron, se eliminan automáticamente para que se use src (inlineado). |
| Los iconos se muestran como texto / fuente Serif sin estilo | Asegúrate de que snapshot.ts capture CSSOM de document.styleSheets (paso 0) y que las fuentes de icono del mismo origen (@font-face) se inlineen como URIs de datos base64. |
/_next/image de Next.js no inlineado | Asegúrate de que el servidor de desarrollo esté ejecutándose cuando se ejecute la instantánea — el script obtiene las imágenes optimizadas del servidor en ejecución. |
| El modo oscuro no se aplica | --html-class dark |
| Banner de cookies en la salida | --remove-fixed |
| La página requiere inicio de sesión | Usa --auth-script ./auth.ts (ver Páginas con Autenticación más abajo) |
| Los gráficos/diagramas se muestran como cuadros en blanco | Usa --inline-canvas para serializar <canvas> a <img> base64 |
Cannot find module 'puppeteer' | npm install -g puppeteer |
Páginas con Autenticación
Para aplicaciones con guardias de inicio de sesión (Vue Router beforeEach, React ProtectedRoute, etc.), crea un pequeño script de autenticación que se ejecute en la sesión de Puppeteer:
// auth-myapp.ts
import type { Page } from 'puppeteer';
export default async function authenticate(page: Page) {
// Ejemplo 1: Llenar y enviar un formulario de inicio de sesión
await page.type('#username', 'admin');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// Ejemplo 2: Inyectar cookies/localStorage directamente
// await page.evaluate(() => {
// localStorage.setItem('token', 'mock-jwt-token');
// });
// Ejemplo 3: Llamar a la API de inicio de sesión de la propia aplicación mediante inyección de módulo (Vue/Vite)
// await page.evaluate(() => {
// return new Promise((resolve) => {
// const script = document.createElement('script');
// script.type = 'module';
// script.textContent = `
// import { useUserStore } from '/src/store/modules/user.ts';
// import { fetchLogin } from '/src/api/auth.ts';
// const res = await fetchLogin({ userName: 'Admin', password: '123456' });
// useUserStore().setToken(res.token, res.refreshToken);
// window.dispatchEvent(new CustomEvent('auth-done'));
// `;
// document.head.appendChild(script);
// window.addEventListener('auth-done', () => resolve(true), { once: true });
// });
// });
}
Luego úsalo:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \
--url http://localhost:5173/#/dashboard \
--output .stitch/dashboard.html \
--auth-script ./auth-myapp.ts \
--inline-canvas \
--wait 5000
El script navega primero a la --url (que puede redirigir al inicio de sesión), ejecuta tu función de autenticación, y luego vuelve a navegar a la --url original con la sesión autenticada.
Estrategia B: Captura con Subagente de Navegador
Úsala cuando necesites interactuar con la página (hacer clic en botones, llenar formularios, navegar por pestañas) antes de la captura. El subagente de navegador te da control total, pero la salida puede truncarse para páginas grandes.
Flujo de Trabajo
-
Inicia la Aplicación localmente.
-
Navega usando un subagente de navegador.
-
Interactúa según sea necesario (hacer clic, desplazarse, llenar formularios).
-
Extrae el DOM:
document.documentElement.outerHTML[!WARNING] Las páginas grandes pueden truncarse. Para manejar esto:
- Elimina las etiquetas
<style>antes de la extracción:document.querySelectorAll('style').forEach(el => el.remove()) - Vuelve a agregar los estilos estáticamente (enlace CDN de Tailwind, CSS fuente)
- Elimina las etiquetas
-
Guarda a archivo.
Apéndice: Respaldo Estático (MockPage.jsx)
[!NOTE] Este método es un último recurso para cuando la aplicación no puede ejecutarse localmente (dependencias rotas, backend faltante, muros de autenticación sin bypass). Requiere aplanar manualmente los componentes React en un solo archivo JSX. Prefiere la Estrategia A siempre que sea posible.
Cuándo Usarlo
- La aplicación no puede ejecutarse localmente en absoluto
- La página requiere autenticación sin mock/bypass
- Necesitas un estado de UI específico que es imposible de alcanzar mediante navegación (pantallas de error, estados vacíos)
Referencia Rápida
npx tsx <SKILL_DIR>/scripts/extract_inline_html.ts \
--index-css src/css/App.css \
--extra-css index.html \
--outdir .stitch \
--page src/MockPage.jsx:Page.html:"Page Title"
Banderas clave: --no-tailwind (apps sin Tailwind), --html-class dark (modo oscuro), --css-files (archivos CSS adicionales).
Detección automática: La configuración de Tailwind se detecta automáticamente. Las directivas @apply usan automáticamente <style type="text/tailwindcss">.
Reglas de MockPage.jsx
- Incluye el diseño completo — encabezado, barra lateral, pie de página (lee
App.jsprimero) - Aplana todos los condicionales — elige un estado, elimina todos los ternarios y guardias
&& - Codifica todos los datos — reemplaza
{variable}con valores concretos, desenrolla los bucles.map() - Preserva los logotipos — usa
<img>con rutas locales (el posprocesamiento los inlineará) - Elimina elementos flotantes — banners de cookies, widgets de chat, botones de retroalimentación
Posprocesamiento
Inlinea imágenes locales:
npx tsx <SKILL_DIR>/scripts/post_process.ts \
.stitch/Page.html --base-dir <directorio-de-la-aplicación>