12k
All articles

Primeros pasos con Octane, el sucesor de Inferno

Octane, sucesor de Inferno, compila componentes estilo React en código DOM directo y explica hooks, arrays de dependencias, instalación y estado beta.

OpenReplay Team
OpenReplay Team
Primeros pasos con Octane, el sucesor de Inferno

Octane es un framework de UI en JavaScript creado por Dominic Gannaway que toma componentes escritos con la API de React (useState, useEffect, memo, contexto, portales, Suspense) y los compila de forma anticipada a código DOM directo, de modo que no hay virtual DOM en lo que llega al navegador.

Si llevas tiempo trabajando con React, algunas de sus reglas pueden empezar a parecer parte del modelo en sí: los hooks se ejecutan en el mismo orden en cada render, los arrays de dependencias se mantienen a mano y un efecto condicional acaba convertido en un componente hijo extraído o en una cláusula de guarda dentro del cuerpo del efecto. La mayoría de esas reglas existen para mantener contento a un reconciliador en tiempo de ejecución, y el reconciliador es justamente la pieza que Octane elimina.

Este artículo cubre qué cambia Octane, cuáles de esos cambios se notan primero, cómo ponerlo en marcha y en qué punto está el proyecto.

Puntos clave

  • Octane compila componentes al estilo de React en operaciones directas sobre el DOM, eliminando el virtual DOM del runtime que se envía al navegador.
  • La identidad de un hook viene de dónde aparece en el código fuente, no del orden en que se ejecutan los hooks, así que un hook puede estar dentro de una rama if o después de un return temprano; los bucles de JavaScript son la única ubicación que el compilador rechaza.
  • Puedes omitir el array de dependencias y dejar que el compilador lea el closure por ti. Si lo escribes tú, significa lo mismo que en React; pasa null para ejecutar en cada render.
  • Necesitas Node.js 22.22.2 o superior para los paquetes publicados, y npm create octane my-app genera el esqueleto de un proyecto.
  • Octane se define a sí mismo como software en beta: el runtime, el compilador y los caminos de SSR/hidratación funcionan, pero las APIs todavía están cambiando.

¿Qué es Octane y quién lo creó?

Octane se presenta como el sucesor de Inferno: la API de React que ya conoces, con el compilador asumiendo tres tareas que hoy realiza el runtime de React, a saber, el virtual DOM, el orden de los hooks y los arrays de dependencias. Gannaway creó Inferno, y entre sus otros trabajos se cuentan React, Lexical, Ripple y Svelte.

Ese linaje es la razón por la que esto merece diez minutos en lugar de un marcador. Inferno buscaba velocidad haciendo que una implementación de virtual DOM fuera tan rápida como razonablemente pudiera serlo, que es la tesis que examinaba nuestro análisis anterior de Inferno.js. Octane mantiene el objetivo de rendimiento como prioridad e invierte el mecanismo: en lugar de un diff más rápido, ningún diff. El encuadre como sucesor procede de los propios materiales de Octane; no hay un anuncio equivalente por parte de Inferno.

La idea del compilador: sin virtual DOM en tiempo de ejecución

Mientras React construye un árbol de descripciones de elementos en cada render y lo reconcilia contra el árbol anterior, Octane compila cada plantilla en un nodo del DOM que se clona en tiempo de ejecución y se parchea directamente. El sitio de documentación del proyecto está construido con Octane, lo cual es una señal razonable de que el compilador soporta una aplicación no trivial.

La consecuencia práctica es que el trabajo que React hace en tiempo de ejecución (recorrer un árbol, comparar props, decidir qué cambió) se decide en tiempo de compilación. El proyecto publica una tabla de benchmarks en su página principal, normalizada respecto a Octane en varias suites. Son cifras del propio proyecto, medidas por el propio proyecto, y la página no publica el hardware ni la fecha de ejecución, así que trátalas como una afirmación por verificar más que como un resultado independiente.

Hooks identificados por su ubicación, no por el orden de llamada

Octane asigna a cada hook su identidad a partir del lugar donde aparece en el código fuente en vez de a partir del orden en que se ejecutan los hooks, y deduce las listas de dependencias omitidas de efectos y memos a partir del closure. Por eso un hook detrás de una condición no supone problema. Este es el cambio con mayor impacto en el día a día.

Esta es la forma que escribes en React, donde el hook tiene que ejecutarse incondicionalmente:

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)} />;
}

El estado y el efecto se elevan por encima de la rama que los necesita, y la lógica de la rama se repite dentro del efecto. En Octane, el hook va donde corresponde:

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)} />;
}

Lo que esto elimina en la práctica: el componente hijo extraído únicamente para hacer condicional un hook, el patrón de “el hook siempre se ejecuta y condicionalmente no hace nada” y los ternarios que existen para mantener estable el número de llamadas. Ten en cuenta que los eventos de Octane vienen directamente del DOM, así que recurre a onInput cuando quieras una actualización en cada pulsación de tecla, mientras que onChange se dispara cuando el navegador confirma la edición.

La única restricción que menciona el proyecto son los bucles de JavaScript. Los hooks se identifican por una ubicación de llamada asignada por el compilador, así que un hook con slot dentro de un bucle for no tiene identidad estable y el compilador lo rechaza. La salida es una lista con claves en la plantilla o un componente hijo por elemento.

¿Por qué los arrays de dependencias son opcionales en Octane?

Omite la lista y el compilador la deduce a partir del closure. Escribe el array tú mismo y se comporta exactamente igual que en React; pasa null cuando quieras que el trabajo se ejecute en cada render. Esto aplica a useEffect, useMemo, useCallback y el resto de hooks que aceptan una lista.

// 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);
});

La vía de escape importa tanto como la inferencia: un array explícito nunca se reescribe, así que allí donde quieras control exacto, escríbelo. Las llamadas directas a los hooks integrados conservan esta inferencia en cualquier módulo que procese el compilador, incluidos los hooks personalizados en archivos .ts o .js planos. Las llamadas a un wrapper propio son un caso más estrecho: el wrapper tiene que estar declarado localmente en un módulo .tsrx o .tsx totalmente compilado, y tiene que pasar su callback y su último parámetro de dependencias directamente a un hook soportado.

¿Cómo poner en marcha octanejs?

Necesitas Node.js 22.22.2 o superior para los paquetes publicados. El comando octane create acepta --template spa para una aplicación solo de cliente o --template fullstack para enrutado, SSR con streaming, hidratación y una build de producción; si omites el flag, te lo pregunta.

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

El gestor de paquetes con el que ejecutes el comando es el que instalará las dependencias, porque un directorio recién creado no tiene ningún lockfile que leer, y por eso la documentación y el repositorio muestran gestores de paquetes distintos para el mismo paso. Para un proyecto que ya tengas, la guía de inicio rápido cubre el camino con Vite: instala octane y @octanejs/vite-plugin, y luego añade el plugin. Ese plugin trae consigo el compilador. Rspack usa @octanejs/rspack-plugin y Rsbuild usa @octanejs/rsbuild-plugin.

TSRX, en breve

TSRX es la sintaxis en la que se escriben los componentes de Octane, contenida en archivos .tsrx, que añade directivas de plantilla (@if, @for, @switch, @try) y bloques <style> con ámbito local junto al marcado al que se aplican. Es un proyecto de lenguaje por derecho propio más que una característica de Octane, y Octane es uno de sus objetivos de compilación junto a React, Preact, Solid, Vue y Ripple. También añade @{ ... }, una forma abreviada para un cuerpo de función que devuelve un único elemento JSX o fragmento, con la preparación arriba y el nodo final como salida. No estás obligado a adoptarlo: la guía TSRX vs TSX/JSX señala que ambos dialectos comparten los mismos hooks, contexto, portales, Suspense, transiciones, eventos nativos, estilos con ámbito, renderizado en servidor e hidratación, y su propio consejo es dejar en paz el TSX que ya funciona en lugar de cambiar extensiones por cambiarlas.

¿En qué punto está realmente Octane?

El proyecto califica a Octane como software en beta: el runtime, el compilador y los caminos de SSR/hidratación funcionan, pero las APIs todavía pueden cambiar antes de la 1.0. El changelog de Octane sitúa las versiones actuales en la línea 0.3, y de ahí se sigue el consejo del inicio rápido de fijar versiones en cualquier proyecto serio. Según el propio recuento del proyecto, la suite principal ejecuta más de 3.900 pruebas de comportamiento independientes, repartidas entre comprobaciones de conformidad, diferenciales, hidratación, runtime, compilador y SSR. Cuánto de la cobertura propia de React representa eso se rastrea caso por caso en un informe de paridad generado, en lugar de deducirse del total de la suite.

La interoperabilidad funciona en ambos sentidos. ReactCompat y OctaneCompat provienen ambos del punto de entrada octane/react: el primero mantiene componentes React reales funcionando dentro de Octane, el segundo inserta componentes Octane compilados en una aplicación React. La guía de compatibilidad con React recorre la configuración de ambos compiladores, el renderizado de una isla de Octane dentro de un árbol de React, la compartición de contexto de React a través de la frontera y el renderizado en servidor con hidratación.

Conviene ser realista respecto al ecosistema. Octane publica ports propios @octanejs/* de librerías de React ampliamente utilizadas, pero el grado de completitud varía: algunas replican el comportamiento original, otras están etiquetadas como parciales o alpha, y la tabla generada docs/bindings-status.md es donde compruebas qué cubre un paquete, qué versión upstream sigue, en qué diverge y si se manejan SSR e hidratación. Un conjunto curado de bindings propios es algo distinto del ecosistema de paquetes del que una aplicación React se sirve sin pensarlo.

Octane es la respuesta más interesante hasta ahora a la pregunta de cómo se ve el modelo de programación de React con el reconciliador en tiempo de ejecución eliminado, y las dos mejoras ergonómicas son lo bastante reales como para percibirlas en una tarde. Consulta la tabla de estado de los bindings para todo aquello de lo que dependas, y luego genera una SPA desechable y coloca un hook dentro de una rama.

Preguntas frecuentes

¿Puedo adoptar Octane dentro de una aplicación React existente sin reescribirla?

Sí. El punto de entrada octane/react exporta OctaneCompat, que da cabida a un subárbol de Octane compilado dentro de un árbol real de React 19, de modo que puedes migrar una pantalla, widget o componente cada vez. Dentro de la isla, use() o useContext leen los contextos de React que la rodean, los eventos siguen siendo nativos y el renderizado en servidor funciona importando el host desde octane/react/server.

¿Sigue funcionando Context.Provider en Octane?

No. La versión 0.3.0 eliminó el alias heredado Context.Provider de los contextos de cliente, servidor y nativos, y el compilador ahora rechaza los accesos a Provider reconocidos estáticamente y te indica qué escribir en su lugar. Usa el propio contexto como componente proveedor y pásale una prop value, o llama a createElement(Theme, { value }, children). El Consumer con render-prop también desapareció, y la página Differences from React de Octane dice que no se añadirá: los hooks con slot permiten que use() o useContext se ejecuten detrás de una condición, que es el problema que Consumer venía a resolver.

¿Hay hooks exentos de la regla de Octane de no usar hooks en bucles?

Sí. use() y useContext no ocupan slot de hook, así que son seguros dentro de un bucle de JavaScript. Cualquier hook con slot, en cambio, compartiría una única ubicación de llamada entre todas las iteraciones, algo que el compilador reporta como error. Las vías documentadas para sortearlo son la directiva @for con claves, que da a cada elemento su propio estado de hooks, o mover el hook a un componente hijo.

¿Por qué onChange se comporta de forma distinta en Octane que en React?

Octane usa eventos reales del DOM delegados, sin capa de eventos sintéticos, así que onChange es el evento change propio del navegador: se dispara cuando la edición se confirma, normalmente al perder el foco, y no en cada pulsación de tecla. Usa onInput para actualizaciones en cada edición. Los inputs controlados siguen cumpliendo las reglas de React para value y checked, y las refs son props corrientes en lugar de algo que se pasa a través de un objeto 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.