Erste Schritte mit Octane, dem Nachfolger von Inferno
Octane, der Nachfolger von Inferno, kompiliert React-artige Komponenten zu direktem DOM-Code und erklärt Hooks, Dependency-Arrays, Setup und Beta-Status.
Octane ist ein JavaScript-UI-Framework von Dominic Gannaway, das Komponenten, die mit der React-API (useState, useEffect, memo, Context, Portals, Suspense) geschrieben wurden, vorab in direkten DOM-Code kompiliert – im Browser landet also kein Virtual DOM.
Wer eine Weile mit React gearbeitet hat, empfindet manche seiner Regeln irgendwann als Teil des Modells selbst: Hooks laufen bei jedem Render in derselben Reihenfolge, Dependency-Arrays werden von Hand gepflegt, und ein bedingter Effect wird zur ausgelagerten Kindkomponente oder zu einer Guard-Klausel im Effect-Body. Die meisten dieser Regeln existieren, um einen Laufzeit-Reconciler bei Laune zu halten – und genau diesen Reconciler entfernt Octane.
Dieser Artikel behandelt, was Octane verändert, welche dieser Änderungen einem zuerst auffallen, wie man es zum Laufen bringt und wie weit das Projekt ist.
Die wichtigsten Erkenntnisse
- Octane kompiliert Komponenten im React-Stil zu direkten DOM-Operationen und entfernt damit das Virtual DOM aus der ausgelieferten Laufzeitumgebung.
- Die Identität eines Hooks ergibt sich aus seiner Position im Quellcode, nicht aus der Reihenfolge, in der Hooks ausgeführt werden. Ein Hook kann also innerhalb eines
if-Zweigs oder nach einem frühen Return stehen; einfache JavaScript-Schleifen sind die einzige Platzierung, die der Compiler ablehnt. - Du kannst ein Dependency-Array weglassen und den Compiler die Closure für dich auswerten lassen. Schreibst du es selbst, bedeutet es dasselbe wie in React; übergib
null, damit bei jedem Render ausgeführt wird. - Für die veröffentlichten Pakete brauchst du Node.js 22.22.2 oder neuer;
npm create octane my-apperzeugt das Projektgerüst. - Octane bezeichnet sich selbst als Beta-Software: Runtime, Compiler sowie die SSR-/Hydration-Pfade funktionieren, aber die APIs sind noch in Bewegung.
Was ist Octane, und wer steckt dahinter?
Octane positioniert sich als Nachfolger von Inferno: die React-API, die du bereits kennst, wobei der Compiler drei Aufgaben übernimmt, die heute die React-Runtime erledigt – Virtual DOM, Hook-Reihenfolge und Dependency-Arrays. Gannaway hat Inferno entwickelt; zu seinen weiteren Arbeiten zählen React, Lexical, Ripple und Svelte.
Diese Herkunft ist der Grund, warum sich hier zehn Minuten lohnen statt nur ein Lesezeichen. Inferno verfolgte Geschwindigkeit, indem es eine Virtual-DOM-Implementierung so schnell machte, wie sie vernünftigerweise sein kann – das war die These, die unser früherer Blick auf Inferno.js untersucht hat. Octane behält das Performance-First-Ziel bei und kehrt den Mechanismus um: statt eines schnelleren Diffs gar kein Diff. Die Nachfolge-Darstellung stammt aus Octanes eigenen Materialien; eine entsprechende Ankündigung auf Inferno-Seite gibt es nicht.
Die Compiler-Idee: kein Virtual DOM zur Laufzeit
Während React bei jedem Render einen Baum aus Elementbeschreibungen aufbaut und ihn mit dem vorherigen Baum abgleicht, kompiliert Octane jedes Template zu einem DOM-Knoten, der zur Laufzeit geklont und direkt gepatcht wird. Die Dokumentationsseite des Projekts ist selbst mit Octane gebaut, was ein vernünftiges Signal dafür ist, dass der Compiler eine nicht-triviale Anwendung bewältigt.
Die praktische Konsequenz: Die Arbeit, die React zur Laufzeit leistet (einen Baum durchlaufen, Props vergleichen, entscheiden, was sich geändert hat), wird stattdessen zur Build-Zeit entschieden. Das Projekt veröffentlicht auf seiner Startseite ein Benchmark-Raster, normalisiert gegen Octane über mehrere Suites hinweg. Das sind die eigenen Zahlen des Projekts, vom Projekt selbst gemessen, und die Seite nennt weder Hardware noch Messdatum – behandle sie also als zu überprüfende Behauptung, nicht als unabhängiges Ergebnis.
Hooks werden nach Call Site verfolgt, nicht nach Aufrufreihenfolge
Octane leitet die Identität jedes Hooks aus der Stelle ab, an der er im Quellcode auftaucht, statt aus der Reihenfolge der Hook-Ausführung, und ermittelt ausgelassene Dependency-Listen für Effects und Memos aus der Closure. Deshalb ist ein Hook hinter einer Bedingung unproblematisch. Das ist die Änderung mit den größten Auswirkungen im Alltag.
So sieht die Struktur in React aus, wo der Hook unbedingt laufen muss:
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)} />;
}
State und Effect werden über den Zweig gehoben, der sie braucht, und die Zweiglogik wird im Effect wiederholt. In Octane steht der Hook dort, wo er hingehört:
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)} />;
}
Was das in der Praxis überflüssig macht: die Kindkomponente, die nur existiert, um einen Hook bedingt zu machen, das Muster „Hook läuft immer und tut bedingt nichts” sowie die Ternaries, die nur da sind, um die Anzahl der Aufrufe stabil zu halten. Beachte, dass Octanes Events direkt aus dem DOM kommen: Greife zu onInput, wenn du bei jedem Tastendruck ein Update willst, während onChange auslöst, wenn der Browser die Eingabe bestätigt.
Die einzige Einschränkung, die das Projekt nennt, sind einfache JavaScript-Schleifen. Hooks werden über die vom Compiler zugewiesene Call Site verschlüsselt, daher hat ein slot-basierter Hook innerhalb einer for-Schleife keine stabile Identität und der Compiler lehnt ihn ab. Der Ausweg ist eine keyed List im Template oder eine Kindkomponente pro Eintrag.
Warum sind Dependency-Arrays in Octane optional?
Lässt du die Liste weg, ermittelt der Compiler sie aus der Closure. Schreibst du das Array selbst, verhält es sich exakt wie in React; übergib null, wenn die Arbeit bei jedem Render laufen soll. Das gilt für useEffect, useMemo, useCallback und die übrigen Hooks, die eine Liste entgegennehmen.
// React: du pflegst die Liste
useEffect(() => {
socket.subscribe(roomId, onMessage);
}, [socket, roomId, onMessage]);
// Octane: der Compiler liest aus, was die Closure erfasst hat
useEffect(() => {
socket.subscribe(roomId, onMessage);
});
Der Escape Hatch ist genauso wichtig wie die Inferenz: Ein explizites Array wird nie umgeschrieben – wo du also exakte Kontrolle willst, schreibst du es hin. Direkte Aufrufe der eingebauten Hooks behalten diese Inferenz in jedem Modul, das der Compiler verarbeitet, einschließlich Custom Hooks in einfachen .ts- oder .js-Dateien. Aufrufe eines eigenen Wrappers sind der engere Fall: Der Wrapper muss lokal in einem vollständig kompilierten .tsrx- oder .tsx-Modul deklariert sein und sowohl seinen Callback als auch seinen letzten Dependency-Parameter unverändert an einen unterstützten Hook durchreichen.
Wie bringt man octanejs zum Laufen?
Für die veröffentlichten Pakete brauchst du Node.js 22.22.2 oder neuer. Der octane create-Befehl nimmt --template spa für eine reine Client-App oder --template fullstack für Routing, Streaming-SSR, Hydration und einen Production-Build entgegen; lässt du das Flag weg, fragt er nach.
npm create octane my-app
cd my-app
npm run dev
Welchen Paketmanager du für den Befehl verwendest, der installiert auch die Abhängigkeiten, denn ein brandneues Verzeichnis hat keine Lockfile zum Auslesen – deshalb zeigen Doku und Repo für denselben Schritt unterschiedliche Paketmanager. Für ein bereits bestehendes Projekt beschreibt der Quick-Start-Guide den Vite-Weg: octane und @octanejs/vite-plugin installieren, dann das Plugin einbinden. Dieses Plugin bringt den Compiler mit. Rspack nutzt @octanejs/rspack-plugin, Rsbuild nutzt @octanejs/rsbuild-plugin.
TSRX, kurz gefasst
TSRX ist die Syntax, in der Octane-Komponenten geschrieben werden, untergebracht in .tsrx-Dateien; sie ergänzt Template-Direktiven (@if, @for, @switch, @try) und gescopte <style>-Blöcke direkt neben dem Markup, für das sie gelten. Es handelt sich um ein eigenständiges Sprachprojekt und nicht um ein Octane-Feature; Octane ist neben React, Preact, Solid, Vue und Ripple eines seiner Compile-Targets. Hinzu kommt @{ ... }, eine Kurzform für einen Funktionsrumpf, der ein einzelnes JSX-Element oder Fragment zurückgibt, mit dem Setup oben und dem finalen Knoten als Ausgabe. Du musst es nicht übernehmen: Der Guide TSRX vs. TSX/JSX betont, dass beide Dialekte dieselben Hooks, Context, Portals, Suspense, Transitions, nativen Events, gescopten Styles, Server-Rendering und Hydration teilen – und empfiehlt selbst, funktionierendes TSX in Ruhe zu lassen, statt Dateiendungen um ihrer selbst willen zu ändern.
Wo steht Octane tatsächlich?
Das Projekt bezeichnet Octane als Beta-Software: Runtime, Compiler sowie die SSR-/Hydration-Pfade funktionieren alle, aber die APIs können sich vor 1.0 noch ändern. Octanes Changelog verortet die aktuellen Releases in der 0.3-Reihe, und die Empfehlung des Quick Starts, Versionen in ernsthaften Projekten zu pinnen, folgt daraus. Nach eigener Zählung des Projekts umfasst die Kern-Suite mehr als 3.900 einzelne Verhaltenstests, verteilt auf Conformance-, Differential-, Hydration-, Runtime-, Compiler- und SSR-Prüfungen. Wie viel von Reacts eigener Abdeckung das darstellt, wird fallweise in einem generierten Parity-Report nachgehalten und nicht aus der Gesamtzahl der Tests abgelesen.
Die Interoperabilität funktioniert in beide Richtungen. ReactCompat und OctaneCompat stammen beide aus dem Entry Point octane/react: Ersteres hält echte React-Komponenten innerhalb von Octane am Laufen, Letzteres setzt kompilierte Octane-Komponenten in eine React-App. Der Guide zur React-Kompatibilität führt durch das Einrichten beider Compiler, das Rendern einer Octane-Insel innerhalb eines React-Baums, das Teilen von React-Context über die Grenze hinweg und Server-Rendering mit Hydration.
Beim Ökosystem lohnt sich ein nüchterner Blick. Octane liefert First-Party-Ports (@octanejs/*) verbreiteter React-Bibliotheken, aber ihre Vollständigkeit variiert: Manche entsprechen dem Verhalten des Originals, andere sind als partial oder alpha gekennzeichnet. In der generierten Tabelle docs/bindings-status.md prüfst du, was ein Paket abdeckt, welcher Upstream-Version es folgt, wo es abweicht und ob SSR und Hydration unterstützt werden. Eine kuratierte Auswahl an First-Party-Bindings ist etwas anderes als das Paket-Ökosystem, aus dem eine React-App ganz selbstverständlich schöpft.
Octane ist die bislang interessanteste Antwort auf die Frage, wie Reacts Programmiermodell ohne Laufzeit-Reconciler aussieht, und die beiden ergonomischen Gewinne sind greifbar genug, um sie innerhalb eines Nachmittags zu spüren. Lies die Bindings-Status-Tabelle für alles, wovon du abhängst, dann erzeuge eine Wegwerf-SPA und setz einen Hook in einen Zweig.
FAQs
Kann ich Octane in einer bestehenden React-App einsetzen, ohne sie neu zu schreiben?
Ja. Der Entry Point octane/react exportiert OctaneCompat, was einem kompilierten Octane-Teilbaum ein Zuhause innerhalb eines echten React-19-Baums gibt – du migrierst also Bildschirm, Widget oder Komponente einzeln. Innerhalb der Insel lesen use() oder useContext die umgebenden React-Contexts, Events bleiben nativ, und Server-Rendering funktioniert, indem du den Host aus octane/react/server importierst.
Funktioniert Context.Provider in Octane weiterhin?
Nein. Das Release 0.3.0 hat den Legacy-Alias Context.Provider aus Client-, Server- und Native-Contexts entfernt, und der Compiler lehnt statisch erkannte Provider-Zugriffe jetzt ab und sagt dir, was du stattdessen schreiben sollst. Verwende den Context selbst als Provider-Komponente und übergib ihm eine value-Prop, oder rufe createElement(Theme, { value }, children) auf. Auch der Render-Prop-Consumer ist weg, und Octanes Seite 'Differences from React' hält fest, dass er nicht wieder aufgenommen wird: Slot-basierte Hooks erlauben es, use() oder useContext hinter einer Bedingung auszuführen – genau das Problem, für das Consumer existierte.
Sind bestimmte Hooks von Octanes Regel 'keine Hooks in Schleifen' ausgenommen?
Ja. use() und useContext belegen keinen Hook-Slot und sind daher innerhalb einer einfachen JavaScript-Schleife unbedenklich. Jeder slot-basierte Hook würde sich dagegen über alle Iterationen hinweg eine einzige Call Site teilen, was der Compiler als Fehler meldet. Die dokumentierten Auswege sind die keyed @for-Direktive, die jedem Eintrag eigenen Hook-State gibt, oder das Verschieben des Hooks in eine Kindkomponente.
Warum verhält sich onChange in Octane anders als in React?
Octane nutzt echte delegierte DOM-Events ohne synthetische Event-Schicht, daher ist onChange das change-Event des Browsers selbst: Es feuert, wenn die Eingabe bestätigt wird, üblicherweise beim Blur, und nicht bei jedem Tastendruck. Verwende onInput für Updates bei jeder Änderung. Controlled Inputs folgen weiterhin Reacts Regeln für value und checked, und Refs sind ganz normale Props statt etwas, das durch ein Wrapper-Objekt gereicht wird.
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