Transformer le pipeline
bext applique une série de transformations du code source lors de la compilation pour optimiser, réécrire et préparer le code d'application pour le rendu côté serveur. Il existe désormais deux pipelines distincts : le chemin de compatibilité Next.js (transformations héritées de la version compatible Vercel) et la passe de compilation PRISM (plie les sous-arbres JSX en chaînes HTML avant que le bundler n'émet des appels d'exécution jsx()).
Les deux pipelines résident sous bext-core::transform, mais ils s'exécutent à des étapes différentes et ciblent différentes configurations de framework.
Architecture#
| Pipeline | Scène | Déclenchement | Sortir |
|---|---|---|---|
| Transformations source | Par fichier, avant le bundle | Plugin NAPI ou appel de compilation du serveur bext | Chaîne source modifiée |
| PRISM passe de compilation | Par fichier, avant le bundle | Nom de fichier [framework] type = "prism" + .tsx / .jsx |
Source avec JSX plié en chaîne concat |
| Transformations post-bundle | Packs After Bun | La sortie du bundle contient useRouter ou process non polyrempli |
Bundle JS corrigé |
| La réponse HTML transforme | Au moment du service, sur le rendu HTML | Toujours | HTML diffusé en continu |
Décisions de conception transversales clés :
- Analyse AST paresseuse — l'AST est uniquement analysé (via
tsc_rs_parser, l'interface tsc-rs) lorsqu'au moins une transformation basée sur AST en a besoin. Les fichiers qui nécessitent uniquement des transformations basées sur des chaînes ignorent complètement l'analyse. - Sortie basée sur la vache — les pipelines utilisent
Cow<str>pour éviter l'allocation une nouvelle chaîne lorsqu'aucune transformation ne modifie le fichier. - Passage unique si possible —
post_processremplace ~120 regex passe de l'implémentation originale de TypeScript avec un seul balayage linéaire. - Ordre déterministe — les transformations s'exécutent dans un ordre fixe
(les numéros de "priorité" référencés par certains anciens documents sont ambitieux - l'implémentation actuelle code en dur la séquence d'appel dans
apply_source_transforms). Les transformations de plugin s'exécutent après tous les éléments intégrés.
La passe de compilation PRISM s'active uniquement lorsque jsx_import_source est "@bext-stack/framework" et que le fichier se termine par .tsx ou .jsx. Les fichiers sans caractère < reçoivent une libération anticipée avant l'analyse de tout AST, il n'y a donc aucune surcharge pour les modules non-JSX.
Passe de compilation PRISM (nouveau)#
La première transformation vue par un site PRISM. Exécute avant que tsc-rs supprime JSX, supprime les sous-arbres JSX pliables de la source et les remplace par des concaténations de chaînes directes. Au moment où le bundler émet
Appels d'exécution jsx(), la plupart du travail est déjà effectué.
Règle d'activation (dans bext-turbopack::direct::transform_with_analysis_opts ) :
options.jsx_import_source == Some("@bext-stack/framework")
&& filename ends with .tsx | .jsx
&& BEXT_PRISM_COMPILE != "0" | "false" | "off"
Six niveaux de pliage (additif, activé par défaut) :
| Étage | Modèle |
|---|---|
| 1 | Sous-arbres JSX entièrement statiques → chaîne littérale |
| 2 | Forme statique + enfant JSX dynamique → prefix + (expr) + suffix |
| 3a | Valeurs d'attr dynamiques + classe ternaire → prefix + __bextEsc(expr) + suffix |
| 4 | Appels array.map(arrow) → (arr).map(...).join("") |
| 5 | Inlining de composants sans prop (récursif) |
| 5.5 | Inlining de composants de roulement d'hélice (param déstructuré) |
Passe au runtime jsx() pour tout ce qui ne peut pas être plié en toute sécurité (attrs spread, props.children, style={{...}}, etc.).
Équivalent en octets vérifié à l’exécution à chaque couche. Sur le luminaire Tailwind actuel de 38,3 Ko validé par octets, le passage est en moyenne de de 15,7 µs (14,0 µs p50), contre de 69,3 µs pour les h() et interprétés directement. 75,3 µs pour le véritable chemin automatique-JSX. Il s'agit d'une réduction du temps d'exécution de 4,4 à 4,8 × et correspond au plafond JavaScript écrit à la main, équivalent en matière de sécurité, dans le bruit de mesure. Voir la passe de compilation PRISM pour la méthodologie et la comparaison complète sur le terrain.
Le pass est mis en œuvre dans bext-core/src/transform/prism_compile.rs (~ 3 840 LOC, 52 cas #[test]). Connecté au bundler à deux endroits :
bext-turbopack/src/direct.rs::compile_closure (source d'entrée) et
transform_with_analysis_opts (importations transitives).
Transformations source (compatibilité Next.js)#
Exécuté par bext-core::transform::apply_source_transforms sur chaque fichier avant le regroupement. Utilisé par les sites compatibles Next.js et par le chemin NAPI du plugin bext. Les sites PRISM n'ont généralement pas besoin de la plupart de ces éléments (pas d'importations Next.js à supprimer, pas de process.env.NEXT_PUBLIC_ à intégrer) : le pipeline fonctionne toujours, mais la plupart des transformations sont anticipées lors de la vérification du contenu.
Les transformations s'exécutent dans un ordre fixe (nombre inférieur = plus tôt) ; chacun est idempotent et tombe proprement lorsqu’il n’est pas nécessaire.
0. Extrait de flux#
bext-core/src/transform/flow_extract.rs — extraits
Définitions de directives "use flow" à partir de fichiers sources et génération de manifestes de flux durables. Les fonctions de flux sont réécrites pour s'inscrire dans le runtime de flux. Basé sur AST.
// Input
"use flow";
export async function onboardUser(userId) { /* ... */ }
// Output: directive removed, function registered with flow runtime
1. Optimisation du baril#
barrel_optimize.rs — convertit les importations de barils (réexportations à partir de fichiers d'index) en importations directes pour permettre le tremblement d'arbre. Plus efficace pour les bibliothèques d'icônes comme lucide-react. Basé sur des chaînes.
// Input
import { Search, Menu, X } from "lucide-react";
// Output
import { Search } from "lucide-react/dist/esm/icons/search";
import { Menu } from "lucide-react/dist/esm/icons/menu";
import { X } from "lucide-react/dist/esm/icons/x";
1b. Optimiser la police#
font_optimize.rs — transforme next/font/google et
next/font/local importe dans un chargement de polices optimisé avec des conseils de préchargement, font-display: swap et des fichiers de polices auto-hébergés. Basé sur des chaînes.
2. Bande d'importation#
import_strip.rs — supprime les importations de packages serveur uniquement qui ne doivent pas apparaître dans les bundles clients. Utilise un automate Aho-Corasick (compilé une fois au démarrage) pour une correspondance efficace de plusieurs modèles avec une liste de paquets interdits configurable.
3. Cale d'injection#
shim_inject.rs — supprime les importations d'effets secondaires pour les marqueurs de limite ( "server-only", "client-only" ) et injecte les cales d'exécution appropriées.
3. Module CSS#
css_module.rs — transforme les importations .module.css en stubs de module CSS qui exportent les mappages de noms de classe. Le CSS réel est extrait et traité séparément.
// Input
import styles from "./button.module.css";
// Output
const styles = { root: "button_root_a1b2c3", label: "button_label_d4e5f6" };
4. Environnement en ligne#
env_inline.rs — remplace les références process.env.NEXT_PUBLIC_* par leurs valeurs littérales à l'aide d'un automate Aho-Corasick précompilé. Permet l'élimination du code mort lorsque les branches dépendantes de l'environnement sont évaluées au moment de la construction.
// Input
if (process.env.NEXT_PUBLIC_FEATURE_FLAG === "true") { /* ... */ }
// Output (NEXT_PUBLIC_FEATURE_FLAG="true")
if ("true" === "true") { /* ... */ }
5. Limite du serveur#
server_boundary.rs — traite les directives "use server" et "use client". Les fonctions marquées par le serveur sont extraites dans un manifeste d'actions du serveur. Les fichiers marqués par le client sont divisés à la limite pour l'hydratation côté client.
6. Directive de cache#
cache_directive.rs — gère la directive "use cache" ( Next.js 15+). Les fonctions marquées "use cache" sont encapsulées avec l'intégration du cache ISR et leurs arguments sont utilisés comme clés de cache.
Transformations post-bundle#
Une fois que Bun a regroupé l'application, une deuxième passe de transformation s'exécute sur la sortie. Implémenté dans bext-core/src/transform/post_process.rs, qui compose :
utiliser le patch du routeur#
use_router_patch.rs — corrige la vérification invariante Next.js useRouter qui est lancée lorsqu'elle est appelée en dehors du contexte d'un routeur. Nécessaire car le SSR de bext n'utilise pas le fournisseur de routeur de Next.js.
Traiter le polyremplissage#
process_polyfill.rs — ajoute un polyfill global process aux bundles qui font référence à process.env en dehors des modèles déjà gérés par Env Inline. Détecte si le polyfill est nécessaire avant l’injection pour éviter une surcharge inutile.
Cale de spécificateur nu#
Transformation fourre-tout (qui réside dans le chemin d'émission du bundle) qui réécrit toutes les importations de spécificateurs nus restantes (importations sans préfixes ./, ../ ou / ) pour pointer vers des modules calés. Les importations DOM React et React sont conservées (jamais shim).
Réécriture de la directive RSC (mode RSC Next.js uniquement)#
rsc_directives.rs — réécrit les directives serveur/client pour le chemin de compilation RSC. Appelé depuis le serveur bext
ssr_pipeline::rsc_server_compile_native lorsqu'un itinéraire est compilé sous --conditions=react-server pour la charge utile Vol.
Transformations de réponse HTML#
Ceux-ci s'exécutent sur la réponse HTML rendue au moment du service (dans bext -server, et non dans bext -core/transform) :
Remplacement de l'îlot de serveur#
Remplace les éléments d'espace réservé <bext-island> par leur contenu rendu par le serveur. Chaque île est rendue indépendamment et injectée dans le flux HTML.
Extraction d'indices de préchargement#
Analyse <head> pour les balises <link rel="stylesheet">, <link rel="preload"> et <script src="..."> pour générer des en-têtes HTTP 103 Early Hints. Permet au navigateur de commencer à télécharger les ressources critiques avant l'arrivée de la réponse complète.
Transformations du plugin#
Les transformations de plug-in s'exécutent après toutes les transformations source intégrées. Ils implémentent le trait TransformPlugin :
pub trait TransformPlugin: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> u32 { 1000 }
fn matches(&self, path: &str, source: &str) -> bool;
fn transform(&self, source: &str, path: &str, ctx: &TransformContext) -> Option<String>;
}
La méthode matches est appelée en premier comme chemin rapide pour ignorer les fichiers qui ne nécessitent pas de traitement. Le TransformContext fournit
Variables d'environnement node_env et NEXT_PUBLIC_.
Résumé du pipeline#
| Scène | Transformer | Caisse / fichier | Taper |
|---|---|---|---|
| Par fichier | Passe de compilation PRISM | bext-core::transform::prism_compile |
Pliage JSX basé sur AST |
| Par fichier | Extrait de flux | bext-core::transform::flow_extract |
AST |
| Par fichier | Optimisation du baril | bext-core::transform::barrel_optimize |
Chaîne |
| Par fichier | Optimiser la police | bext-core::transform::font_optimize |
Chaîne |
| Par fichier | Bande d'importation | bext-core::transform::import_strip |
AST + Aho-Corasick |
| Par fichier | Cale d'injection | bext-core::transform::shim_inject |
Chaîne |
| Par fichier | Module CSS | bext-core::transform::css_module |
Chaîne |
| Par fichier | Env en ligne | bext-core::transform::env_inline |
Aho-Corasick |
| Par fichier | Limite du serveur | bext-core::transform::server_boundary |
AST |
| Par fichier | Directive de cache | bext-core::transform::cache_directive |
AST |
| Par fichier (RSC) | Directives de la SRC | bext-core::transform::rsc_directives |
Chaîne |
| Post-lot | utiliser le patch du routeur | bext-core::transform::use_router_patch |
Chaîne |
| Post-lot | Traiter le polyremplissage | bext-core::transform::process_polyfill |
Chaîne |
| Post-lot | Cale de spécification nue | bext-turbopack::direct |
Chaîne |
| Réponse | Remplacement de l'îlot | bext-server |
Chaîne |
| Réponse | Extraction de précharge | bext-server |
Chaîne |
| N'importe où | Transformations de plugins | par plugin | Défini par le plugin |
Toutes les transformations par fichier sont automatiques et ne peuvent pas être désactivées individuellement. Si le pré-filtre d'une transformation (par ex.
source.contains("import") ou source.contains("use server") ) ne correspond pas au contenu du fichier, la transformation est ignorée sans aucun coût. Idem pour la passe de compilation PRISM : les fichiers sans < bénéficient d'une libération anticipée avant l'analyse AST.
Se désinscrire#
| Pipeline | Comment |
|---|---|
| Passe de compilation PRISM | Var d'environnement BEXT_PRISM_COMPILE=0 (par build) |
| Transformations sources | Non désactivable individuellement. Utilisez un type de framework différent ( [framework] type = "static" ignore entièrement les transformations source). |
| Transformations du plugin | Ne chargez pas le plugin |
Références croisées#
- Pipeline de rendu PRISM — où ces transformations s'exécutent en une seule requête, de bout en bout
- Frontend TypeScript (tsc-rs) — l'analyseur/AST/émetteur qui alimente les transformations basées sur AST
- PRISM — le framework que la compilation passe cibles
- Passe de compilation PRISM — complet référence pour ce que la passe plie, ce qu'elle saute et les données de performances
- Présentation de l'architecture — espace de travail structure, où chaque transformation vit
- Extensions — écriture de transformations personnalisées