Reprise (resumability)
Un îlot à signaux classique « s'hydrate » : le composant est ré-exécuté sur le client pour reconstruire le graphe réactif. La reprise (modèle Qwik) fait l'inverse — le serveur sérialise les valeurs des signaux dans le HTML, et le client reprend l'interactivité sans jamais exécuter la fonction du composant. Activée par la directive "use resumable".
computed(), les lectures multi-signaux et les gestionnaires (y compris typés TS) reprennent. Chaque îlot pèse ~1,5 Ko ; en mode lazy, zéro JS n'est téléchargé avant la première interaction.Comment l'utiliser
Écrivez un composant à signaux normal avec la directive « use resumable », puis rendez-le avec resumableIsland :
"use resumable";
/** @jsxImportSource @bext-stack/framework/signals */
import { signal, computed } from "@bext-stack/framework/signals";
export default function Counter(props) {
const count = signal(props.start ?? 0);
return (
<button onClick={() => count.value++}>
Count: {count.value}
</button>
);
}import { resumableIsland } from "@bext-stack/framework/signals";
import Counter from "./Counter";
// Eager — resumes on load. Ships ~1.5KB (just the lifted handlers/reads)
// plus a shared ~3.5KB runtime cached once across every island and page.
<Raw html={resumableIsland("Counter", Counter, { start: 5 })} />
// Lazy — ZERO island JS on load. The bundle downloads on the first
// interaction, resumes, and the loader replays the event.
<Raw html={resumableIsland("Counter", Counter, { start: 5 }, { lazy: true })} />Démos en direct
Compteur — la base. Démarre à 5 (valeur serveur) ; au clic, seul le gestionnaire lifté s'exécute et l'état reprend (5 → 6). La fonction du composant ne tourne jamais sur le client.
Bascule — une lecture conditionnelle ET un attribut réactif (aria-expanded) qui reprennent ensemble.
Bouton « j'aime » — deux signaux (compteur + état aimé) et un gestionnaire qui touche les deux ; la pluralisation est une lecture conditionnelle.
Multi-signaux + computed, paresseux — a, b et total = a + b. Cet îlot est « lazy » : son bundle n'est PAS téléchargé au chargement. Au premier clic, il se télécharge, reprend, puis rejoue l'événement. (Onglet réseau : /islands/ResumableSum.js n'apparaît qu'après le clic.)
Gestionnaire typé TS — son corps porte « (e: Event) » et « (e.target as HTMLInputElement) ». Le mini-bundle l'intègre tel quel, donc le compilateur le dé-type en JS valide. Tapez : l'écho se met à jour.
Comment ça marche
Le compilateur lifte chaque gestionnaire et chaque lecture réactive en facteurs autonomes ; le serveur sérialise les valeurs des signaux dans le HTML :
// The compiler lifts each handler + reactive read into a tiny, self-contained
// factory map (the component function itself is NEVER shipped to the client):
export const __resume = {
rh: { "Counter@0": (scope) => { const count = scope.count; return () => count.value++; } },
rexpr: { "Counter$0": (scope) => { const count = scope.count; return count.value; } },
ccomp: {},
};
// The server serializes signal VALUES + a marker→read map into the HTML:
<bext-island data-component="Counter" data-runtime="resumable">
<script type="application/bext-resume">
{"signals":{"count":5},"computeds":[],"markers":{"0":"Counter$0"}}
</script>
<button data-bs-onclick="Counter@0">Count: <!--bs0-->5<!--/bs0--></button>
</bext-island>Au client, un runtime de reprise partagé (~3,5 Ko, mis en cache une fois pour tous les îlots) reconstruit les signaux à partir des valeurs sérialisées, recrée les computed, câble chaque marqueur DOM avec un effect, et résout les gestionnaires à l'événement — sans jamais ré-exécuter le composant. En mode lazy, rien ne se télécharge avant l'interaction ; le premier événement est capturé puis rejoué après la reprise.