12k
All articles

Prise en main d'Octane, le successeur d'Inferno

Octane, successeur dInferno, compile des composants de style React en code DOM direct et explique hooks, dépendances, installation et statut bêta.

OpenReplay Team
OpenReplay Team
Prise en main d'Octane, le successeur d'Inferno

Octane est un framework UI JavaScript signé Dominic Gannaway qui prend des composants écrits avec l’API de React (useState, useEffect, memo, contexte, portails, Suspense) et les compile en amont en code DOM direct, de sorte qu’il n’y a aucun DOM virtuel dans ce qui est livré au navigateur.

Si vous travaillez avec React depuis un certain temps, certaines de ses règles finissent par sembler faire partie du modèle lui-même : les hooks s’exécutent dans le même ordre à chaque rendu, les tableaux de dépendances sont maintenus à la main, et un effet conditionnel se transforme en composant enfant extrait ou en clause de garde à l’intérieur du corps de l’effet. La plupart de ces règles existent pour satisfaire un réconciliateur d’exécution, et le réconciliateur est précisément la pièce qu’Octane supprime.

Cet article couvre ce qu’Octane change, celles de ces évolutions que l’on remarque en premier, comment le mettre en route, et où en est le projet.

Points clés

  • Octane compile les composants de style React en opérations DOM directes, supprimant le DOM virtuel du runtime livré.
  • L’identité d’un hook provient de sa position dans le code source, et non de l’ordre d’exécution des hooks : un hook peut donc se trouver dans une branche if ou après un retour anticipé ; les boucles JavaScript classiques constituent le seul emplacement rejeté par le compilateur.
  • Vous pouvez omettre un tableau de dépendances et laisser le compilateur lire la closure pour vous. Si vous l’écrivez vous-même, il a exactement le même sens que dans React ; passez null pour une exécution à chaque rendu.
  • Node.js 22.22.2 ou une version plus récente est requis pour les paquets publiés, et npm create octane my-app échafaude un projet.
  • Octane se présente comme un logiciel en bêta : le runtime, le compilateur et les chemins SSR/hydratation fonctionnent, mais les API évoluent encore.

Qu’est-ce qu’Octane, et qui l’a conçu ?

Octane se positionne comme le successeur d’Inferno : l’API React que vous connaissez déjà, avec un compilateur qui reprend à son compte trois tâches aujourd’hui assurées par le runtime React, à savoir le DOM virtuel, l’ordonnancement des hooks et les tableaux de dépendances. Gannaway a créé Inferno, et son parcours inclut également React, Lexical, Ripple et Svelte.

Cette filiation explique pourquoi le sujet mérite dix minutes plutôt qu’un simple signet. Inferno recherchait la vitesse en rendant une implémentation de DOM virtuel aussi rapide qu’elle pouvait raisonnablement l’être, thèse qu’examinait notre précédent tour d’horizon d’Inferno.js. Octane conserve l’objectif de performance avant tout et en inverse le mécanisme : au lieu d’un diff plus rapide, pas de diff du tout. Le cadrage « successeur » provient des supports d’Octane eux-mêmes ; aucune annonce correspondante n’existe côté Inferno.

L’idée du compilateur : pas de DOM virtuel à l’exécution

Là où React construit un arbre de descriptions d’éléments à chaque rendu et le réconcilie avec l’arbre précédent, Octane compile chaque template en un nœud DOM cloné à l’exécution puis patché directement. Le site de documentation du projet est lui-même construit avec Octane, signal raisonnable que le compilateur tient la route sur une application non triviale.

La conséquence pratique est que le travail effectué par React à l’exécution (parcourir un arbre, comparer les props, décider de ce qui a changé) est tranché au moment du build. Le projet publie une grille de benchmarks sur sa page d’accueil, normalisée par rapport à Octane sur plusieurs suites. Il s’agit des chiffres du projet, mesurés par le projet, et la page n’indique ni le matériel ni la date d’exécution : considérez-les donc comme une affirmation à vérifier plutôt que comme un résultat indépendant.

Des hooks identifiés par site d’appel, et non par ordre d’appel

Octane confère à chaque hook son identité à partir de l’endroit où il apparaît dans le code source plutôt qu’à partir de l’ordre d’exécution des hooks, et il déduit de la closure les listes de dépendances omises pour les effets et les mémos. C’est pourquoi un hook placé derrière une condition ne pose aucun problème. C’est le changement qui a le plus de conséquences au quotidien.

Voici la forme que vous écrivez en React, où le hook doit s’exécuter inconditionnellement :

function Panel({ isEditing }: Props) {
  const [draft, setDraft] = useState('');
  useEffect(() => {
    if (!isEditing) return;
    syncDraft(draft);
  }, [isEditing, draft]);

  if (!isEditing) return <Readonly />;
  return <Editor value={draft} onChange={e => setDraft(e.target.value)} />;
}

L’état et l’effet sont remontés au-dessus de la branche qui en a besoin, et la logique de branchement est répétée à l’intérieur de l’effet. Dans Octane, le hook se place là où il doit être :

function Panel({ isEditing }: Props) {
  if (!isEditing) return <Readonly />;

  const [draft, setDraft] = useState('');
  useEffect(() => syncDraft(draft));
  return <Editor value={draft} onInput={e => setDraft(e.currentTarget.value)} />;
}

Ce que cela élimine en pratique : le composant enfant extrait uniquement pour rendre un hook conditionnel, le motif « le hook s’exécute toujours et ne fait conditionnellement rien », et les ternaires qui n’existent que pour maintenir stable le nombre d’appels. Notez que les événements d’Octane proviennent directement du DOM : utilisez donc onInput lorsque vous voulez une mise à jour à chaque frappe, tandis qu’onChange se déclenche quand le navigateur valide la modification.

La seule restriction que mentionne le projet concerne les boucles JavaScript classiques. Les hooks sont indexés par un site d’appel assigné par le compilateur : un hook associé à un slot placé dans une boucle for n’a donc aucune identité stable, et le compilateur le rejette. Une liste avec clés dans le template ou un composant enfant par élément constitue la voie de sortie.

Pourquoi les tableaux de dépendances sont-ils optionnels dans Octane ?

Omettez la liste et le compilateur la déduit de la closure. Écrivez le tableau vous-même et il se comporte exactement comme dans React ; passez null quand vous voulez que le traitement s’exécute à chaque rendu. Cela vaut pour useEffect, useMemo, useCallback et les autres hooks qui acceptent une liste.

// React: you maintain the list
useEffect(() => {
  socket.subscribe(roomId, onMessage);
}, [socket, roomId, onMessage]);

// Octane: the compiler reads what the closure captured
useEffect(() => {
  socket.subscribe(roomId, onMessage);
});

L’échappatoire compte autant que l’inférence : un tableau explicite n’est jamais réécrit, donc partout où vous voulez un contrôle exact, écrivez-le. Les appels directs aux hooks intégrés conservent cette inférence dans tout module traité par le compilateur, y compris les hooks personnalisés dans de simples fichiers .ts ou .js. Les appels à un wrapper que vous avez écrit constituent un cas plus restreint : le wrapper doit être déclaré localement dans un module .tsrx ou .tsx entièrement compilé, et il doit transmettre son callback et son dernier paramètre de dépendances directement à un hook pris en charge.

Comment faire tourner octanejs ?

Node.js 22.22.2 ou une version plus récente est requis pour les paquets publiés. La commande octane create accepte --template spa pour une application côté client uniquement, ou --template fullstack pour le routage, le SSR en streaming, l’hydratation et un build de production ; omettez l’option et elle vous posera la question.

npm create octane my-app
cd my-app
npm run dev

Le gestionnaire de paquets avec lequel vous exécutez la commande est celui qui installera les dépendances, car un répertoire tout neuf ne contient aucun lockfile à lire : c’est pourquoi la documentation et le dépôt présentent des gestionnaires de paquets différents pour la même étape. Pour un projet existant, le guide de démarrage rapide couvre la voie Vite : installez octane et @octanejs/vite-plugin, puis ajoutez le plugin. Ce plugin embarque le compilateur. Rspack utilise @octanejs/rspack-plugin et Rsbuild @octanejs/rsbuild-plugin.

TSRX, en bref

TSRX est la syntaxe dans laquelle les composants Octane sont écrits, portée par des fichiers .tsrx, qui ajoute des directives de template (@if, @for, @switch, @try) et des blocs <style> à portée limitée placés à côté du balisage auquel ils s’appliquent. Il s’agit d’un projet de langage à part entière plutôt que d’une fonctionnalité d’Octane, et Octane n’est que l’une de ses cibles de compilation, aux côtés de React, Preact, Solid, Vue et Ripple. TSRX ajoute également @{ ... }, un raccourci pour un corps de fonction qui retourne un unique élément ou fragment JSX, avec la phase d’initialisation en haut et le nœud final en sortie. Rien ne vous oblige à l’adopter : le guide TSRX vs TSX/JSX souligne que les deux dialectes partagent les mêmes hooks, le même contexte, les mêmes portails, Suspense, transitions, événements natifs, styles à portée limitée, rendu serveur et hydratation, et son propre conseil est de laisser tel quel un code TSX qui fonctionne plutôt que de changer d’extension pour le principe.

Où en est réellement Octane ?

Le projet qualifie Octane de logiciel en bêta : le runtime, le compilateur et les chemins SSR/hydratation fonctionnent tous, mais les API peuvent encore évoluer avant la 1.0. Le changelog d’Octane situe les versions actuelles dans la lignée 0.3, et le conseil du démarrage rapide d’épingler les versions dans tout projet sérieux en découle. Selon le décompte du projet lui-même, la suite principale exécute plus de 3 900 tests comportementaux distincts, répartis entre vérifications de conformité, différentielles, d’hydratation, de runtime, de compilation et de SSR. La part de la couverture propre à React que cela représente est suivie cas par cas dans un rapport de parité généré, et non déduite du total de la suite.

L’interopérabilité fonctionne dans les deux sens. ReactCompat et OctaneCompat proviennent tous deux du point d’entrée octane/react : le premier permet à de véritables composants React de s’exécuter au sein d’Octane, le second insère des composants Octane compilés dans une application React. Le guide de compatibilité React détaille la configuration des deux compilateurs, le rendu d’un îlot Octane au sein d’un arbre React, le partage du contexte React par-delà la frontière, et le rendu serveur avec hydratation.

Soyez lucide quant à l’écosystème. Octane livre des portages first-party @octanejs/* de bibliothèques React largement utilisées, mais leur degré d’achèvement varie : certains reproduisent le comportement amont, d’autres sont étiquetés partiels ou alpha, et c’est dans le tableau généré docs/bindings-status.md que vous vérifierez ce que couvre un paquet, quelle version amont il suit, où il diverge, et si le SSR et l’hydratation sont pris en charge. Un ensemble sélectionné de bindings first-party est une tout autre chose que l’écosystème de paquets dans lequel une application React puise sans même y penser.

Octane constitue à ce jour la réponse la plus intéressante à la question de savoir à quoi ressemble le modèle de programmation de React une fois le réconciliateur d’exécution retiré, et les deux gains ergonomiques sont suffisamment réels pour se faire sentir en l’espace d’un après-midi. Consultez le tableau d’état des bindings pour tout ce dont vous dépendez, puis échafaudez une SPA jetable et placez un hook à l’intérieur d’une branche.

FAQ

Puis-je adopter Octane au sein d'une application React existante sans tout réécrire ?

Oui. Le point d'entrée octane/react exporte OctaneCompat, qui accueille un sous-arbre Octane compilé à l'intérieur d'un véritable arbre React 19 : vous migrez ainsi un écran, un widget ou un composant à la fois. À l'intérieur de l'îlot, use() ou useContext lit les contextes React environnants, les événements restent natifs, et le rendu serveur fonctionne en important l'hôte depuis octane/react/server.

Context.Provider fonctionne-t-il toujours dans Octane ?

Non. La version 0.3.0 a supprimé l'alias historique Context.Provider des contextes client, serveur et natifs, et le compilateur rejette désormais tout accès à Provider reconnu statiquement en vous indiquant quoi écrire à la place. Utilisez le contexte lui-même comme composant fournisseur et passez-lui une prop value, ou appelez createElement(Theme, { value }, children). Le Consumer à render prop a également disparu, et la page « Differences from React » d'Octane précise qu'il ne sera pas ajouté : les hooks indexés par slot permettent à use() ou useContext de s'exécuter derrière une condition, ce qui était précisément le problème que Consumer résolvait.

Certains hooks échappent-ils à la règle d'Octane interdisant les hooks dans les boucles ?

Oui. use() et useContext n'occupent aucun slot de hook : ils sont donc sûrs à l'intérieur d'une boucle JavaScript classique. Tout hook indexé par slot partagerait au contraire un unique site d'appel entre toutes les itérations, ce que le compilateur signale comme une erreur. Les contournements documentés sont la directive @for avec clés, qui donne à chaque élément son propre état de hook, ou le déplacement du hook dans un composant enfant.

Pourquoi onChange se comporte-t-il différemment dans Octane et dans React ?

Octane utilise de véritables événements DOM délégués, sans couche d'événements synthétiques : onChange correspond donc à l'événement change natif du navigateur, qui se déclenche lorsque la modification est validée, généralement au blur, plutôt qu'à chaque frappe. Utilisez onInput pour des mises à jour à chaque saisie. Les champs contrôlés suivent toujours les règles de React pour value et checked, et les refs sont des props ordinaires plutôt qu'un élément transmis via un objet wrapper.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.