Rechargement en direct
bext prend en charge le rechargement en direct de type PHP : modifiez un fichier source et le navigateur se met à jour automatiquement. Pas de redémarrage, pas de rafraîchissement manuel. Le serveur surveille les modifications de fichiers, déclenche une reconstruction optimisée, échange atomiquement le pool V8 et informe les navigateurs connectés d'une actualisation logicielle.
Comment ça marche#
Developer saves a file
│
▼
File watcher detects mtime change (1s poll)
│
▼
Debounce (200ms) — wait for batch saves
│
▼
Fast or full rebuild (see below)
│
▼
Create new V8 pool from fresh bundle
│
▼
Atomic swap (RwLock) — in-flight requests finish on old pool
│
▼
Clear ISR + component + compression caches
│
▼
Notify browsers (SSE event or ETag change)
│
▼
Browser soft-refreshes — swaps content + styles, no full reload
Configuration#
# bext.config.toml
[build]
watch_dirs = ["server"]
live_reload = true # enable in production (always on in dev mode)
debounce_ms = 200 # wait for batch saves (default: 200ms)
| Clé | Taper | Défaut | Description |
|---|---|---|---|
watch_dirs |
string[] |
[] |
Répertoires à surveiller pour les changements |
live_reload |
bool |
false |
Activer le rechargement automatique en production |
debounce_ms |
u64 |
200 |
Fenêtre anti-rebond pour les sauvegardes par lots |
script |
string |
(aucun) | Script de construction à exécuter (reconstruction complète uniquement) |
bun_path |
string |
"bun" |
Chemin vers le binaire Bun |
timeout_secs |
u64 |
60 |
Temps de construction maximum avant expiration du délai |
live_reload = true dans bext.config.toml est requis pour permettre à l'observateur en production. En mode développement ( bext run . ), il est toujours activé quelle que soit la configuration.
Deux modes de reconstruction#
L'observateur sélectionne automatiquement le meilleur mode en fonction de ce qui a changé :
| Fichiers modifiés | Mode | Vitesse | Qu'est-ce qui court |
|---|---|---|---|
.ts, .tsx, .js, .jsx |
Rapide | ~50 ms | Direct bun build entry.ts – ignore le script de construction |
.css, .json, .toml |
Complet | ~200 ms | Exécute votre [build] script (Tailwind, etc.) |
Le chemin rapide appelle le bundler de Bun directement avec le fichier d'entrée, en contournant le script de construction externe. Cela signifie que la modification d'une page ou le rechargement du gestionnaire d'itinéraire en moins de 100 ms au total.
Actualisation côté client#
Lorsque le serveur est reconstruit, les navigateurs doivent le savoir. Le framework propose trois modes de notification :
Détection automatique (recommandé)#
import { clientRuntime } from "@bext-stack/framework/client";
// In your template's <body>:
{clientRuntime({ liveReload: true })}
Avec liveReload: true, le client essaie d'abord SSE (latence instantanée d'environ 0 ms). Si la fonctionnalité realtime n'est pas activée sur le serveur, il revient à l'interrogation ETag (latence d'environ 2 s). Aucune configuration nécessaire : cela fonctionne.
Mode SSE (instantané, nécessite une fonctionnalité en temps réel)#
{clientRuntime({ liveReload: "sse" })}
Lorsque le serveur échange le pool, il envoie un événement « reload » via les événements envoyés par le serveur. Le navigateur le reçoit instantanément et s'actualise en douceur. Nécessite [realtime] enabled = true dans votre configuration bext.
Mode sondage (fonctionne partout)#
{clientRuntime({ liveReload: "poll", pollInterval: 2000 })}
Le navigateur envoie une requête HEAD toutes les 2 secondes et vérifie l'en-tête ETag. Lorsque le pool est échangé, tous les caches sont effacés, de sorte que la requête suivante a un ETag différent. Le client détecte le changement et effectue une actualisation logicielle.
Pas de rechargement en direct (juste la navigation client)#
{clientRuntime({ navigation: true, liveReload: false })}
La navigation côté client fonctionne toujours (interception de lien de type Turbo, pushState, prélecture au survol) mais pas d'actualisation automatique lors des modifications du serveur.
À quoi sert le "soft rafraîchissement" ?#
Lorsqu'une reconstruction est détectée, le client n'effectue pas de rechargement complet de la page. Plutôt:
- récupère le HTML de la page actuelle via
fetch() - met à jour les balises
<link rel="stylesheet">: de nouvelles URL supprimées du cache sont ajoutées et les URL obsolètes sont supprimées. - met à jour les balises
<style>— les styles en ligne sont remplacés par un nouveau contenu - échange le contenu de
<main>— le corps de la page est mis à jour sans toucher à<nav>,<header>,<footer> - utilise l'API View Transitions si disponible — animation de fondu enchaîné fluide
Cela signifie:
- L'état de navigation est préservé (la position de défilement est réinitialisée vers le haut)
- Les feuilles de style externes avec des URL supprimées du cache extraient toujours le dernier CSS
- Les styles en ligne (courants dans les sites de modèles) sont toujours nouveaux
- Pas de flash de contenu sans style
Navigation côté client#
Le runtime client fournit également une navigation de type SPA (activée par défaut) :
{clientRuntime({ navigation: true })}
Caractéristiques:
- Interception de lien — les clics
<a>de même origine récupèrent le code HTML et échangent<main>, pas de chargement de page complète history.pushState— Mises à jour d'URL sans rechargement, fonctionne en arrière/avant- Pré-récupération au survol — après un survol de 65 ms, pré-récupère la page cible
- Afficher l'API des transitions — fondu enchaîné fluide lorsqu'il est disponible
- Fallback — les liens externes, les téléchargements, les touches de modification (Ctrl+clic) fonctionnent toujours normalement
Combiné avec le rechargement en direct :
{clientRuntime({ navigation: true, liveReload: true })}
Cela vous donne des transitions de page instantanées ET des mises à jour automatiques lorsque vous modifiez des fichiers sources.
Actifs détruits par le cache#
Pour Tailwind ou tout autre CSS externe, utilisez des noms de fichiers hachés avec le contenu :
// Build script
import { buildCSS } from "@bext-stack/framework/tailwind";
await buildCSS({
sources: ["server"],
outFile: "dist/styles.[hash].css", // → styles.18vjj5s.css
manifest: "dist/css-manifest.json", // → { "styles.*.css": "styles.18vjj5s.css" }
});
// Template
import { loadManifest, asset, stylesheet } from "@bext-stack/framework";
loadManifest("dist/css-manifest.json");
function RootLayout({ children }) {
return (
<html>
<head>
{stylesheet(asset("/styles.css"))} {/* → /styles.18vjj5s.css */}
</head>
<body>
<main>{children}</main>
{clientRuntime({ liveReload: true })}
</body>
</html>
);
}
Lorsque le CSS change, la construction produit un nouveau hachage, le manifeste est mis à jour et le modèle fait référence à la nouvelle URL. L'actualisation logicielle du client détecte la balise <link> modifiée et charge la nouvelle feuille de style.
Trois méthodes de déclenchement#
1. Automatique (observateur de fichiers)#
Activé par watch_dirs + live_reload = true. Montres les fichiers .ts, .tsx, .js, .jsx, .css, .json, .toml. Ignore node_modules, .git, dist.
2. Point de terminaison de l'API#
# Full rebuild (runs build script)
curl -X POST http://localhost:3000/__bext/admin/api/bundle/reload
# Fast rebuild (TS only, skips build script)
curl -X POST http://localhost:3000/__bext/admin/api/bundle/fast-reload
Réponse:
{
"ok": true,
"mode": "fast",
"bundle_kb": 44,
"build_ms": 48,
"load_ms": 12,
"total_ms": 60
}
3. Signal SOUPIR#
kill -HUP $(pidof bext-server)
Utile pour déployer des scripts et l'intégration de systemd.
Sécurité de production#
Le rechargement en direct est sans danger pour la production :
- Aucune demande abandonnée.
RwLockpermet des lectures simultanées (chaque requête) avec des écritures rares (échange de pool). Le verrou en écriture bloque les nouvelles requêtes pendant quelques microsecondes. - Les builds échouées sont inoffensives. Si la génération échoue, l'ancien pool et les caches restent intacts. Une erreur est enregistrée, le serveur continue de fonctionner.
- Mémoire limitée. L'ancien pool est supprimé une fois les demandes en cours terminées. Aucune fuite suite à des rechargements répétés.
- Corriger les caches. Les caches ISR, composants et compression sont effacés de manière atomique après l'échange. Aucun contenu obsolète.
Temps de construction#
| Phase | Durée |
|---|---|
| Détection de changement de fichier | ~1 s (intervalle d'interrogation) |
| Fenêtre anti-rebond | 200 ms (configurable) |
| Reconstruction rapide (TS uniquement) | 30-80 ms |
| Reconstruction complète (avec Tailwind) | 100-300 ms |
| Création du pool V8 (4 travailleurs) | 5-15 ms |
| Échange de pool atomique | < 1 ms |
| Notification client (SSE) | < 1 ms |
| Notification client (sondage) | ~2s |
| Total (rapide + SSE) | ~250 ms |
| Total (complet + sondage) | ~3,5 s |
Exemple : site de documentation avec aperçu instantané#
# bext.config.toml
[server]
port = 3021
[build]
watch_dirs = ["server", "content"]
live_reload = true
debounce_ms = 300
// server/entry.ts
import { createSite } from "@bext-stack/framework/entry";
import { defineRoutes } from "@bext-stack/framework/router";
import { clientRuntime } from "@bext-stack/framework/client";
import { stylesheet } from "@bext-stack/framework";
import { h } from "@bext-stack/framework";
import * as pages from "./pages";
const router = defineRoutes({
layout: ({ children }) => (
<html>
<head>
{stylesheet("/styles.css")}
</head>
<body>
<nav><a href="/">Home</a> | <a href="/docs">Docs</a></nav>
<main>{children}</main>
{clientRuntime({ navigation: true, liveReload: true })}
</body>
</html>
),
routes: {
"/": { page: () => <h1>Welcome</h1> },
"/docs/[...path]": {
page: (ctx) => {
const content = pages.getPage("/docs/" + ctx.params.path);
return content
? <article dangerouslySetInnerHTML={{ __html: content.html }} />
: <p>Doc not found.</p>;
},
},
},
});
createSite({ hostname: "docs.example.com", router });
Modifiez n'importe quel fichier dans server/ ou content/ → le navigateur se met à jour en moins d'une seconde, avec des transitions fluides et sans rechargement complet de la page.
En rapport#
- Exécution client — la fonction
clientRuntime()qui pilote l'actualisation côté navigateur - Cadre PRISM — Observateur par itinéraire de PRISM (séparé du rechargement en direct du site modèle)
- Cadre de modèle — Le rechargement en direct des
createSite()etSiteConfigfonctionne à l'intérieur - CSS vent arrière — la version CSS qui déclenche le mode de reconstruction complète lors des modifications du fichier
.css - Configuration : créer des indicateurs —
watch_dirs,debounce_mset toutes les clés de configuration associées