Extraire du HTML statique
Extrayez un fichier HTML statique autonome à partir de n'importe quelle application web.
Quelle stratégie utiliser
Vous DEVEZ demander à l'utilisateur de choisir la stratégie à utiliser avant de procéder. Présentez clairement les options, recommandez la Stratégie A comme valeur par défaut préférée, et fournissez un bref résumé des avantages/inconvénients pour chaque option afin de l'aider à prendre une décision éclairée.
| Stratégie A (Puppeteer) | Stratégie B (Sous-agent Navigateur) | |
|---|---|---|
| Quand | L'application s'exécute localement, sans mur d'authentification | Nécessité d'interagir d'abord avec la page (clic, remplissage de formulaires) |
| Fidélité | La plus élevée — styles calculés résolus | Élevée — DOM rendu |
| Configuration | Aucune — pas de maquette nécessaire | Aucune — pas de maquette nécessaire |
| Framework | Tout | Tout |
| Sortie | Écrit dans un fichier — pas de limite de taille | Peut être tronqué dans le contexte de l'agent |
[!WARNING] Point de contrôle — Confirmation de l'utilisateur requise. Vous DEVEZ demander à l'utilisateur quelle stratégie il préfère avant de procéder. Présentez le tableau de comparaison ci-dessus, recommandez la Stratégie A par défaut, et attendez une approbation explicite. Ne prenez PAS la décision vous-même et ne continuez pas tant que l'utilisateur n'a pas confirmé.
Stratégie A : Capture par Puppeteer (Recommandée)
Lance Chrome headless, capture le DOM entièrement rendu et produit un fichier HTML autonome avec tous les CSS intégrés et les images encodées en base64. Fonctionne avec tout framework — pas de MockPage.jsx nécessaire.
Prérequis
- Application s'exécutant localement (par exemple,
npm run dev) - Node.js avec
puppeteerdisponible (vérifier :node -e "require('puppeteer')")
Workflow
-
Démarrez l'application et notez le port.
[!WARNING] Point de contrôle — Confirmation de l'utilisateur requise. Après avoir démarré le serveur local, vous DEVEZ faire une pause et demander à l'utilisateur la confirmation avant d'exécuter le script de capture ou de lancer un sous-agent navigateur. Signalez l'URL et le port à l'utilisateur afin qu'il puisse vérifier que l'application s'exécute et s'affiche correctement. Ne procédez PAS à l'étape de capture tant que l'utilisateur n'a pas confirmé.
-
Exécutez le script de capture :
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000 -
Plusieurs pages — exécutez une fois par route :
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 -
Nettoyez le serveur de développement : Si un serveur de développement local a été démarré spécifiquement pour la capture instantanée, assurez-vous d'arrêter le processus du serveur ou de terminer la tâche en arrière-plan une fois la capture terminée.
Indicateurs du script
| Indicateur | Défaut | Description |
|---|---|---|
--url | (obligatoire) | URL à capturer |
--output | (obligatoire) | Chemin du fichier de sortie |
--wait | 1000 | Attente supplémentaire (ms) après l'inactivité du réseau. Augmentez pour les applications à chargement paresseux. |
--viewport | 1280x800 | Taille de la fenêtre d'affichage sous la forme LARGEURxHAUTEUR |
--html-class | — | Classe(s) pour l'élément <html> (par exemple, dark) |
--remove-fixed | false | Supprime les éléments fixes/collants (bannières de cookies, widgets de chat) |
--full-height | false | Redimensionne la fenêtre d'affichage à la hauteur totale du défilement |
--title | — | Remplace le titre de la page (définir sur le chemin de la route, par exemple /dashboard ou /settings/profile) |
--auth-script | — | Chemin vers un module JS/TS qui exporte une fonction par défaut async (page) => void pour l'authentification |
--inline-canvas | false | Convertit les éléments <canvas> (ECharts, Chart.js, D3) en balises <img> base64 |
Ce qu'il fait automatiquement
- Capture toutes les règles CSSOM à partir de
document.styleSheets(préserve les styles de développement dynamiques Vite/Tailwind et CSS-in-JS) - Intègre tous les
<link rel="stylesheet">→ blocs<style> - Convertit les
srcetsrcsetdes<img>→ URI de données base64 (ignore les polices externes) - Intègre les fichiers de polices d'icônes de même origine et relatifs (
@font-face) sous forme d'URI de données base64 afin que les ligatures ne s'affichent jamais sous forme de texte ASCII - Intègre les URL
<source srcset>en base64 - Supprime les entrées
srcsetéchouées/mortes afin que le navigateur utilise lasrcintégrée comme solution de repli - Supprime les balises
<script>, les blocs de style de développement Vite HMR (createHotContext,import.meta.hot) et les overlays de développement - Résout les chemins CSS relatifs
url()avant l'intégration
Notes sur les frameworks
| Framework | Notes |
|---|---|
| React + Vite | Fonctionne directement. --wait 1000. |
| Next.js | --wait 3000 pour l'hydratation SSR. URL : http://localhost:3000. <img srcset> de /_next/image est intégré automatiquement en base64. |
| Angular (@angular/cli / v17+) | Fonctionne directement avec ng serve (URL par défaut : http://localhost:4200). --wait 2000 pour l'hydratation des animations Angular Material / PrimeNG et les routes à chargement paresseux. |
| Vue / Nuxt | Fonctionne directement. |
| Svelte / SvelteKit | Fonctionne directement. |
| Storybook | Utilisez l'URL de l'histoire : --url http://localhost:6006/?path=/story/... |
| SSR (Webpack) | Peut nécessiter un --wait plus long. |
Dépannage
| Problème | Solution |
|---|---|
| Images manquantes | Augmentez --wait |
| Les images s'affichent comme cassées après l'arrêt du serveur | Vérifiez que srcset a été intégré — consultez le journal pour "N images intégrées". Si les URL srcset ont échoué, elles sont automatiquement supprimées afin que src (intégré) soit utilisé. |
| Les icônes s'affichent sous forme de texte / police Serif sans style | Assurez-vous que snapshot.ts capture CSSOM à partir de document.styleSheets (étape 0) et que les polices d'icônes de même origine (@font-face) sont intégrées sous forme d'URI de données base64. |
Next.js /_next/image non intégré | Assurez-vous que le serveur de développement est en cours d'exécution lors de la capture — le script récupère les images optimisées à partir du serveur en cours d'exécution. |
| Mode sombre non appliqué | --html-class dark |
| Bannière de cookies dans la sortie | --remove-fixed |
| La page nécessite une connexion | Utilisez --auth-script ./auth.ts (voir la section Pages nécessitant une authentification ci-dessous) |
| Les graphiques/diagrammes s'affichent sous forme de cases vides | Utilisez --inline-canvas pour sérialiser <canvas> en <img> base64 |
Cannot find module 'puppeteer' | npm install -g puppeteer |
Pages nécessitant une authentification
Pour les applications avec des gardes de connexion (Vue Router beforeEach, React ProtectedRoute, etc.), créez un petit script d'authentification qui s'exécute dans la session Puppeteer :
// auth-monapp.ts
import type { Page } from 'puppeteer';
export default async function authenticate(page: Page) {
// Exemple 1 : Remplir et soumettre un formulaire de connexion
await page.type('#username', 'admin');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// Exemple 2 : Injecter des cookies/localStorage directement
// await page.evaluate(() => {
// localStorage.setItem('token', 'jeton-mock');
// });
// Exemple 3 : Appeler l'API de connexion de l'application via l'injection de module (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 });
// });
// });
}
Utilisez-le ensuite :
npx tsx <SKILL_DIR>/scripts/snapshot.ts \
--url http://localhost:5173/#/dashboard \
--output .stitch/dashboard.html \
--auth-script ./auth-monapp.ts \
--inline-canvas \
--wait 5000
Le script navigue d'abord vers l'--url (qui peut rediriger vers la connexion), exécute votre fonction d'authentification, puis re-navigue vers l'--url d'origine avec la session authentifiée.
Stratégie B : Capture par sous-agent navigateur
À utiliser lorsque vous devez interagir avec la page (cliquer sur des boutons, remplir des formulaires, naviguer entre les onglets) avant la capture. Le sous-agent navigateur vous donne un contrôle total, mais la sortie peut être tronquée pour les grandes pages.
Workflow
-
Démarrez l'application localement.
-
Naviguez à l'aide d'un sous-agent navigateur.
-
Interagissez selon les besoins (clic, défilement, remplissage de formulaires).
-
Extrayez le DOM :
document.documentElement.outerHTML[!WARNING] Les grandes pages peuvent être tronquées. Pour gérer cela :
- Supprimez les balises
<style>avant l'extraction :document.querySelectorAll('style').forEach(el => el.remove()) - Réajoutez les styles de manière statique (lien CDN Tailwind, CSS source)
- Supprimez les balises
-
Sauvegardez dans un fichier.
Annexe : Repli statique (MockPage.jsx)
[!NOTE] Cette méthode est un dernier recours pour les cas où l'application ne peut pas être exécutée localement (dépendances cassées, backend manquant, murs d'authentification sans contournement). Elle nécessite d'aplatir manuellement les composants React en un seul fichier JSX. Privilégiez la Stratégie A autant que possible.
Quand l'utiliser
- L'application ne peut pas du tout être exécutée localement
- La page nécessite une authentification sans maquette/contournement
- Vous avez besoin d'un état d'interface utilisateur spécifique impossible à atteindre par la navigation (écrans d'erreur, états vides)
Référence rapide
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:"Titre de la page"
Indicateurs clés : --no-tailwind (applications non Tailwind), --html-class dark (mode sombre), --css-files (fichiers CSS supplémentaires).
Détection automatique : La configuration Tailwind est détectée automatiquement. Les directives @apply utilisent automatiquement <style type="text/tailwindcss">.
Règles MockPage.jsx
- Incluez la mise en page complète — en-tête, barre latérale, pied de page (lisez d'abord
App.js) - Aplatissez tous les conditionnels — choisissez un état, supprimez toutes les ternaires et gardes
&& - Codifiez en dur toutes les données — remplacez
{variable}par des valeurs concrètes, déroulez les boucles.map() - Conservez les logos — utilisez
<img>avec des chemins locaux (le post-traitement les intégrera) - Supprimez les éléments flottants — bannières de cookies, widgets de chat, boutons de commentaires
Post-traitement
Intégrez les images locales :
npx tsx <SKILL_DIR>/scripts/post_process.ts \
.stitch/Page.html --base-dir <répertoire-de-l'application>