Extrair HTML Estático
Extrai um arquivo HTML estático autocontido de qualquer aplicação web.
Qual Estratégia Usar
Você DEVE perguntar ao usuário qual estratégia usar antes de prosseguir. Apresente as opções claramente, recomende a Estratégia A como padrão preferido e forneça um breve resumo de prós e contras para cada opção para ajudá-los a tomar uma decisão informada.
| Estratégia A (Puppeteer) | Estratégia B (Subagente de Navegador) | |
|---|---|---|
| Quando | A aplicação roda localmente, sem barreira de autenticação | Precisa interagir com a página primeiro (clicar, preencher formulários) |
| Fidelidade | A maior — estilos computados resolvidos | Alta — DOM renderizado |
| Configuração | Zero — nenhum mock necessário | Zero — nenhum mock necessário |
| Framework | Qualquer | Qualquer |
| Saída | Grava em arquivo — sem limite de tamanho | Pode truncar no contexto do agente |
[!WARNING] Ponto de Verificação — Confirmação do Usuário Necessária. Você DEVE perguntar ao usuário qual estratégia ele prefere antes de prosseguir. Apresente a tabela de comparação acima, recomende a Estratégia A como padrão e aguarde aprovação explícita. NÃO tome a decisão sozinho nem prossiga até que o usuário confirme.
Estratégia A: Instantâneo com Puppeteer (Recomendado)
Inicia o Chrome headless, captura o DOM totalmente renderizado e produz um arquivo HTML autocontido com todo o CSS embutido e imagens como base64. Funciona com qualquer framework — não precisa de MockPage.jsx.
Pré-requisitos
- Aplicação rodando localmente (ex.:
npm run dev) - Node.js com
puppeteerdisponível (verificar:node -e "require('puppeteer')")
Fluxo de Trabalho
-
Inicie a Aplicação e anote a porta.
[!WARNING] Ponto de Verificação — Confirmação do Usuário Necessária. Após iniciar o servidor local, você DEVE pausar e pedir confirmação ao usuário antes de executar o script de instantâneo ou iniciar um subagente de navegador. Informe a URL e a porta ao usuário para que ele possa verificar se a aplicação está rodando e renderizando corretamente. NÃO prossiga para a etapa de instantâneo até que o usuário confirme.
-
Execute o Script de Instantâneo:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000 -
Múltiplas páginas — execute uma vez por rota:
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 -
Limpe o Servidor de Desenvolvimento: Se um servidor de desenvolvimento local foi iniciado especificamente para a extração do instantâneo, certifique-se de parar o processo do servidor ou encerrar a tarefa em segundo plano assim que a extração for concluída.
Flags do Script
| Flag | Padrão | Descrição |
|---|---|---|
--url | (obrigatório) | URL a ser capturada |
--output | (obrigatório) | Caminho do arquivo de saída |
--wait | 1000 | Espera extra (ms) após a rede ficar ociosa. Aumente para aplicações com carregamento preguiçoso. |
--viewport | 1280x800 | Tamanho da viewport como LARGURAxALTURA |
--html-class | — | Classe(s) para o elemento <html> (ex.: dark) |
--remove-fixed | false | Remove elementos fixos/sticky (banners de cookie, widgets de chat) |
--full-height | false | Redimensiona a viewport para a altura total de rolagem |
--title | — | Sobrescreve o título da página (definir como o caminho da rota, ex. /dashboard ou /settings/profile) |
--auth-script | — | Caminho para um módulo JS/TS que exporta uma função async (page) => void padrão para autenticação |
--inline-canvas | false | Converte elementos <canvas> (ECharts, Chart.js, D3) para tags <img> base64 |
O Que Ele Faz Automaticamente
- Captura todas as regras CSSOM de
document.styleSheets(preserva estilos dinâmicos de desenvolvimento Vite/Tailwind e CSS-in-JS) - Embuti todos os
<link rel="stylesheet">→ blocos<style> - Converte
srcde<img>esrcset→ URIs de dados base64 (ignora fontes externas) - Embuti arquivos de fontes de ícones (
@font-face) de mesma origem e relativos como URIs de dados base64 para que ligaduras nunca sejam renderizadas como texto ASCII - Embuti URLs de
<source srcset>como base64 - Remove entradas
srcsetcom falha/mortas para que o navegador use osrcembutido como fallback - Remove tags
<script>, blocos de estilo de desenvolvimento Vite HMR (createHotContext,import.meta.hot) e sobreposições de desenvolvimento - Resolve caminhos relativos de
url()em CSS antes de embutir
Notas por Framework
| Framework | Notas |
|---|---|
| React + Vite | Funciona imediatamente. --wait 1000. |
| Next.js | --wait 3000 para hidratação SSR. URL: http://localhost:3000. <img srcset> de /_next/image é automaticamente embutido como base64. |
| Angular (@angular/cli / v17+) | Funciona imediatamente com ng serve (URL padrão: http://localhost:4200). --wait 2000 para hidratação de animação do Angular Material / PrimeNG e rotas carregadas preguiçosamente. |
| Vue / Nuxt | Funciona imediatamente. |
| Svelte / SvelteKit | Funciona imediatamente. |
| Storybook | Use a URL da história: --url http://localhost:6006/?path=/story/... |
| SSR (Webpack) | Pode precisar de --wait maior. |
Solução de Problemas
| Problema | Solução |
|---|---|
| Imagens ausentes | Aumente --wait |
| Imagens aparecem quebradas após o servidor parar | Verifique se srcset foi embutido — veja o log para "Imagens embutidas: N". Se as URLs de srcset falharam, elas são removidas automaticamente para que src (embutido) seja usado. |
| Ícones exibidos como texto / Fonte serifa sem estilo | Certifique-se de que snapshot.ts captura CSSOM de document.styleSheets (passo 0) e que fontes de ícones de mesma origem (@font-face) são embutidas como URIs de dados base64. |
/_next/image do Next.js não embutido | Certifique-se de que o servidor de desenvolvimento está rodando quando o instantâneo é executado — o script busca imagens otimizadas do servidor em execução. |
| Modo escuro não aplicado | --html-class dark |
| Banner de cookie na saída | --remove-fixed |
| Página requer login | Use --auth-script ./auth.ts (veja Páginas com Autenticação abaixo) |
| Gráficos/plotagens mostrados como caixas em branco | Use --inline-canvas para serializar <canvas> para <img> base64 |
Cannot find module 'puppeteer' | npm install -g puppeteer |
Páginas com Autenticação
Para aplicações com guardas de login (Vue Router beforeEach, React ProtectedRoute, etc.), crie um pequeno script de autenticação que é executado na sessão do Puppeteer:
// auth-myapp.ts
import type { Page } from 'puppeteer';
export default async function authenticate(page: Page) {
// Exemplo 1: Preencher e enviar um formulário de login
await page.type('#username', 'admin');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// Exemplo 2: Injetar cookies/localStorage diretamente
// await page.evaluate(() => {
// localStorage.setItem('token', 'mock-jwt-token');
// });
// Exemplo 3: Chamar a própria API de login da aplicação via injeção 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 });
// });
// });
}
Em seguida, use-o:
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
O script navega primeiro para a --url (que pode redirecionar para o login), executa sua função de autenticação e então re-navega para a --url original com a sessão autenticada.
Estratégia B: Captura com Subagente de Navegador
Use quando precisar interagir com a página (clicar em botões, preencher formulários, navegar por abas) antes de capturar. O subagente de navegador oferece controle total, mas a saída pode truncar para páginas grandes.
Fluxo de Trabalho
-
Inicie a Aplicação localmente.
-
Navegue usando um subagente de navegador.
-
Interaja conforme necessário (clicar, rolar, preencher formulários).
-
Extraia o DOM:
document.documentElement.outerHTML[!WARNING] Páginas grandes podem truncar. Para lidar com isso:
- Remova tags
<style>antes da extração:document.querySelectorAll('style').forEach(el => el.remove()) - Re-adicione estilos estaticamente (link CDN do Tailwind, CSS de origem)
- Remova tags
-
Salve em arquivo.
Apêndice: Fallback Estático (MockPage.jsx)
[!NOTE] Este método é um último recurso para quando a aplicação não pode ser executada localmente (dependências quebradas, backend ausente, barreiras de autenticação sem alternativa). Requer achatar manualmente os componentes React em um único arquivo JSX. Prefira a Estratégia A sempre que possível.
Quando Usar
- A aplicação não pode ser executada localmente
- A página requer autenticação sem mock/alternativa
- Você precisa de um estado de UI específico que é impossível de alcançar por navegação (telas de erro, estados vazios)
Referência 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:"Título da Página"
Flags principais: --no-tailwind (aplicações não-Tailwind), --html-class dark (modo escuro), --css-files (arquivos CSS extras).
Detecção automática: A configuração do Tailwind é detectada automaticamente. Diretivas @apply usam automaticamente <style type="text/tailwindcss">.
Regras do MockPage.jsx
- Inclua o layout completo — cabeçalho, barra lateral, rodapé (leia
App.jsprimeiro) - Achate todos os condicionais — escolha um estado, remova todos os ternários e guardas
&& - Codifique todos os dados — substitua
{variable}por valores concretos, desenrole loops.map() - Preserve logotipos — use
<img>com caminhos locais (o pós-processamento os embutirá) - Remova elementos flutuantes — banners de cookie, widgets de chat, botões de feedback
Pós-Processamento
Embutir imagens locais:
npx tsx <SKILL_DIR>/scripts/post_process.ts \
.stitch/Page.html --base-dir <diretório-da-aplicação>