ENbext.dev
DocumentationPRISM (natif bext)

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

Sur cette page

PRISM

PRISM est le framework natif de bext. JSX se compile pour diriger les chaînes HTML via le runtime h() — il n'y a pas de React sur le serveur, pas de DOM virtuel, pas de différence. Une passe de compilation au moment de la construction fournie dans bext-core plie des arborescences de composants entières en concaténations de chaînes uniques avant que tout travail d'exécution ne se produise.

Sur le catalogue Tailwind actuel de 38,3 Ko, la passe de compilation s'affiche en 15,7 µs en moyenne et le véritable chemin d'exécution automatique JSX en 75,3 µs. React 19 renderToString prend 1 823 µs pour le même DOM. Voir Performance pour le luminaire, l'exécution et la méthodologie.

Le sigle est décoratif. Lisez-le comme "la façon dont bext restitue TSX sur le serveur" et continuez.

PRISM est sponsorisé par Encre Conception Web 29

Démarrage rapide#

Un site PRISM minimal :

bash
mkdir my-site && cd my-site
mkdir -p src/app

src/app/page.tsx:

tsx
export default function HomePage(props: {
  searchParams?: { name?: string };
}) {
  const name = props.searchParams?.name ?? "world";
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <title>my-site</title>
      </head>
      <body>
        <h1>Hello, {name}!</h1>
        <p>Rendered by PRISM (no React on the server).</p>
      </body>
    </html>
  );
}

bext.config.toml:

toml
[server]
port = 3088
app_dir = "."

[framework]
type = "prism"

tsconfig.json:

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@bext-stack/framework"
  }
}

package.json:

json
{
  "dependencies": {
    "@bext-stack/framework": "workspace:*"
  }
}

Courir:

bash
bext run .

C'est tout le cadre. Aucun script de build package.json, non vite.config.ts, pas de next.config.mjs. La première requête déclenche une compilation par route (~ 30-200 ms à froid) ; chaque requête suivante est un isolat V8 chaleureux avec des contextes de page mis en cache (rendu d'environ 1 à 5 ms).

Comment ça marche#

Le moteur d'exécution h()#

Le runtime JSX de PRISM se trouve sur @bext-stack/framework/jsx-runtime. Il exporte jsx, jsxs, Fragment et (depuis le 29/04/2026) escapeHtml. Le mode react-jsx de TypeScript importe automatiquement jsx / jsxs sur chaque site d'appel JSX.

tsx
// What you write:
<div className="card">
  <h2>{title}</h2>
</div>

// What tsc-rs emits (when no compile pass runs):
jsx("div", { className: "card", children: jsx("h2", { children: title }) })

// What h() does (simplified):
function h(tag, props, ...children) {
  if (typeof tag === "function") return tag(mergeChildren(props, children));
  const attrs = formatAttrs(props);          // " class=\"card\""
  if (isVoidElement(tag)) return safe(`<${tag}${attrs}>`);
  return safe(`<${tag}${attrs}>${renderChildren(children)}</${tag}>`);
}

Trois propriétés rendent cela réalisable :

  1. Les chaînes simples sont échappées ; Le code HTML rendu porte la marque — h() s'enroule sous-arbres rendus dans SafeHtml. Un parent transmet cette marque, tandis qu'une expression de chaîne simple est échappée. Cela empêche à la fois l’injection et la double évasion sans marqueur intra-bande dans la réponse.

  2. Le chemin asynchrone est AsyncIterable<string> — lorsqu'un enfant est un Promise ou AsyncIterable, h() produit des morceaux : ouvrez la balise, puis chaque enfant résolu, puis fermez la balise. Le protocole de streaming dans @bext-stack/framework/streaming construit Suspense au-dessus de cette primitive.

  3. Les attributs et expressions enfants sont des limites d'échappement — dynamique les attributs passent par formatAttrs ; les enfants à cordes simples passent par escapeHtml. Voir HTML s'échappant pour savoir ce que cela signifie dans la pratique.

La passe de compilation#

Lorsque [framework] type = "prism" est défini, le pipeline de transformation bext - turbopack exécute prism_compile.rs sur chaque fichier .tsx / .jsx dans la fermeture transitive de l'itinéraire. Le pass est implémenté en tant que visiteur de style SWC sur tsc_rs_ast (pas de dépôt swc - utilise le propre analyseur TypeScript de bext).

Ce qu'il plie :

Modèle Sortir
<head><meta charSet="utf-8"/></head> __bextSafe("<head><meta charset=\"utf-8\"></head>")
<h1>Hello, {name}!</h1> __bextSafe("<h1>Hello, " + __bextChild(name) + "!</h1>")
<div className={cond ? "a" : "b"}> '<div class="' + __bextEsc(cond ? "a" : "b") + '">'
<ul>{items.map(i => <li>{i}</li>)}</ul> un accumulateur IIFE avec une boucle for et un texte dynamique échappé
<Header/> (zéro-hélice) insère récursivement le corps de Header
<Card title="x" body={user.bio}/> (paramètre déstructuré) corps en ligne avec substitution d'accessoires

Récursif : un parent contenant un composant enfant contenant un .map() contenant une classe ternaire — tous se réduisent en une seule expression concaténée. Sur l'appareil de pliage documenté, 0 sur 11 appels jsx() restent dans le bundle compilé.

Le laissez-passer est par défaut et sécurisé par construction : tout ce qu'il ne peut pas prouver statiquement en toute sécurité passe au runtime jsx(). Cautions sur :

  • Attributs de diffusion ( <div {...rest}> )
  • Composants avec le paramètre de style Ident function Card(props) — uniquement le function Card({ a, b }) déstructuré est pris en charge
  • Références props.children — pourrait être AsyncIterable<string>, la contrainte de chaîne rendrait [object AsyncGenerator]
  • style={{...}} et dangerouslySetInnerHTML={{...}} — exécution applique la sémantique des objets
  • Appels de composants avec des enfants ( <Card>{slot}</Card> )
  • Flèches au corps de bloc avec effets secondaires dans les rappels .map()
  • Fonctions asynchrones/générateurs

Désactivez-vous avec BEXT_PRISM_COMPILE=0 dans l'environnement. Utile pour les versions différentes ; inutile en utilisation normale.

Voir le document de compilation pour plus de détails.

Routage du système de fichiers#

code
src/app/
  page.tsx                    → /
  about/page.tsx              → /about
  blog/[slug]/page.tsx        → /blog/:slug (dynamic param)
  shop/[...rest]/page.tsx     → /shop/* (catch-all)
  api/users/route.ts          → /api/users (HTTP API)
  islands/Counter.tsx         → /islands/Counter.js (browser bundle)
  components/MyComp.tsx       → not routed (server-only library)

Les mises en page enveloppent les pages par répertoire :

code
src/app/
  layout.tsx                  ← root layout (renders for every route)
  blog/
    layout.tsx                ← nested, wraps every blog/*
    page.tsx                  ← /blog
    [slug]/
      page.tsx                ← /blog/:slug

Le répartiteur compose les mises en page à l'envers : Layout0(Layout1(Layout2(Page(props)))). Chaque mise en page reçoit children comme premier accessoire, ainsi que le contexte complet de la demande.

Actions du serveur#

PRISM expose un modèle de mutation de style Remix : publiez un <form> sur /_bext/action/<exportName>, expédition via le actions.ts colocalisé :

tsx
// src/actions/auth.ts
"use server";

export async function login(req: Request): Promise<Response> {
  const body = await req.formData();
  // ... auth logic ...
  return new Response(null, {
    status: 302,
    headers: { Location: "/dashboard", "Set-Cookie": "session=..." },
  });
}
tsx
// somewhere in JSX
<form method="post" action="/_bext/action/login">
  <input name="email" />
  <input name="password" type="password" />
  <button>Sign in</button>
</form>

Le répartiteur parcourt src/actions/*.ts et trouve les fichiers commençant par "use server", analyse leurs exportations. Tout POST /_bext/action/<exportName> est envoyé à la fonction correspondante. Voir Conventions de données PRISM pour les conventions de chargeur/action colocalisées avec les fichiers de route.

Îles#

Pour l'interactivité, marquez un composant avec "use client" :

tsx
// src/islands/Counter.tsx
"use client";

import { useState } from "preact/hooks";

export default function Counter({ start = 0 }: { start?: number }) {
  const [n, setN] = useState(start);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

Utilisez-le depuis un composant serveur :

tsx
import Counter from "../islands/Counter";

export default function Page() {
  return (
    <main>
      <Counter start={42} />
    </main>
  );
}

Le serveur affiche un espace réservé <bext-island> ; le chargeur client récupère /islands/Counter.js, analyse les accessoires de data-props et appelle le mount(el, props) du composant. Les îles peuvent utiliser n'importe quel framework côté client ( React, Preact, Solid, Vanilla DOM) - le serveur s'en fiche, il expédie simplement le bundle.

Performance#

Numéros du harnais jsx-shootout sur une page de liste de produits de 38,3 Ko validée en octets. Les mesures utilisent Bun 1.3.13 sur un noyau épinglé ; les valeurs PRISM /Solid sont des médianes d'exécutions alternées :

Moteur moyenne p50 Débit
Constructeur de chaînes Rust 3,9 µs 3,9 µs 255 000 rendus/s
Plafond JS sécurisé roulé à la main 15,8 µs 14,3 µs 63 000 rendus/s
PRISM passe de compilation 15,7µs 14,0 µs 64K rendus/s
Marc 5 38 µs 22 µs 26 000 rendus/s
PRISM interprété, direct h() 69,3 µs 62,1 µs 14,4K rendus/s
PRISM automatique JSX 75,3 µs 67,9 µs 13,3K rendus/s
Solide 1.9 ( generate: "ssr" ) 78 µs 61 µs 12,8 000 rendus/s
React 19 1 823 µs 1 646 µs 549 rendus/s

Le PRISM compilé est environ 2,4 fois plus rapide que Marko et 116 fois plus rapide que le React sur cet appareil, tout en correspondant au plafond JavaScript écrit à la main, équivalent en matière de sécurité, dans le bruit de mesure. La ligne Rust est une limite inférieure native, et non une implémentation JavaScript de PRISM.

PRISM et Solid interprétés sont désormais dans la même bande : Solid a le p50 inférieur, tandis que PRISM a la moyenne légèrement inférieure dans les dernières exécutions alternées. Le runtime automatique réutilise les accessoires intrinsèques créés par le compilateur au lieu de les cloner, contourne la normalisation des composants redondants lorsque les enfants sont déjà normalisés et conserve deux résultats exacts d'échappement de classe pour les lignes répétées du tableau. Le cache est limité à deux entrées et stocke la valeur d'échappement, de sorte que les classes dynamiques répétées n'affaiblissent pas le contrat d'échappement HTML.

Pour le contexte, une mesure historique distincte de bout en bout HTTP TTFB sur sites/status (la page d'état bext.dev, un site de production PRISM) a enregistré :

Mode moyenne p95
BEXT_PRISM_COMPILE=0 17,59 ms 23,69 ms
par défaut (compilation activée) 6,50 ms 8,39 ms

Cette exécution a enregistré 2,71 fois plus rapide TTFB. Il n'a pas été réexécuté dans le cadre de la fusillade actuelle de rendu de 38,3 Ko. Sa différence de 4 à 5 ms entre le microbench SSR et HTTP TTFB était la surcharge du framework HTTP (TCP, routage actix, écriture de réponse), non affectée par la passe.

Quand utiliser PRISM#

Cas d'utilisation Choix
Marketing public / documents / blog PRISM — pages zéro JS, rendus en microsecondes
Statut / tableau de bord / administrateur en lecture principale PRISM + quelques îlots pour les bits interactifs
Contenu basé sur les magazines/CMS PRISM + cache ISR (TTL par route dans bext.config.toml)
Application avec soumissions de formulaires, redirections PRISM + actions serveur
Application interactive en temps réel (chat, éditeur) Next.js / React (les îles PRISM ne sont pas un SPA complet)
Base de code React / Next.js existante Restez sur Next.js, utilisez bext comme interface HTTP ( type = "nextjs" )

PRISM est votre propre framework, dans votre dépôt. Il n'y a pas Surface de l'API @bext-stack/framework au-delà de ce qu'elle contient sites/shared/framework/ — lancez le runtime, ajoutez des composants, expédiez. L'équipe bext utilise PRISM pour bext.dev, status.bext.dev, docs.bext.dev et une poignée de sites internes ; la passe de compilation a été développée contre ceux-ci.

Conseil

Les enfants de l'expression PRISM ( {value} ) sont automatiquement échappés lorsque value est une chaîne simple. Les sous-arbres rendus portent la marque SafeHtml hors bande et passent sans double échappement. Utilisez dangerouslySetInnerHTML uniquement pour du HTML riche fiable ou déjà nettoyé. Voir HTML s'échappant.

Conseil

Si vous souhaitez un routage de système de fichiers imbriqué sans écrire manuellement un routeur, consultez Routage et mises en page — defineRoutes() gère la composition de la disposition.

Quelle est la prochaine étape#

En rapport#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.