ENbext.dev
DocumentationPRISM (natif bext)

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

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#

code
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#

toml
# 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
Conseil

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é)#

tsx
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)#

tsx
{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)#

tsx
{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)#

tsx
{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:

  1. récupère le HTML de la page actuelle via fetch()
  2. 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.
  3. met à jour les balises <style> — les styles en ligne sont remplacés par un nouveau contenu
  4. échange le contenu de <main> — le corps de la page est mis à jour sans toucher à <nav>, <header>, <footer>
  5. 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

Le runtime client fournit également une navigation de type SPA (activée par défaut) :

tsx
{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 :

tsx
{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 :

ts
// 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" }
});
tsx
// 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#

bash
# 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:

json
{
  "ok": true,
  "mode": "fast",
  "bundle_kb": 44,
  "build_ms": 48,
  "load_ms": 12,
  "total_ms": 60
}

3. Signal SOUPIR#

bash
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. RwLock permet 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é#

toml
# bext.config.toml
[server]
port = 3021

[build]
watch_dirs = ["server", "content"]
live_reload = true
debounce_ms = 300
tsx
// 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() et SiteConfig fonctionne à 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_ms et toutes les clés de configuration associées
Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.