12k
All articles

Primeiros Passos com o Octane, o Sucessor do Inferno

Octane, sucessor do Inferno, compila componentes no estilo React em código DOM direto e explica hooks, arrays de dependências, setup e status beta.

OpenReplay Team
OpenReplay Team
Primeiros Passos com o Octane, o Sucessor do Inferno

Octane é um framework de UI em JavaScript criado por Dominic Gannaway que pega componentes escritos com a API do React (useState, useEffect, memo, context, portals, Suspense) e os compila antecipadamente em código DOM direto, de modo que não existe virtual DOM naquilo que é entregue ao navegador.

Se você trabalha com React há algum tempo, algumas de suas regras começam a parecer parte do próprio modelo: hooks executam na mesma ordem em cada render, arrays de dependências são mantidos manualmente e um efeito condicional se transforma em um componente filho extraído ou em uma cláusula de guarda dentro do corpo do efeito. A maioria dessas regras existe para manter um reconciliador em runtime satisfeito — e o reconciliador é justamente a peça que o Octane remove.

Este artigo cobre o que o Octane muda, quais dessas mudanças você percebe primeiro, como colocá-lo para funcionar e em que estágio o projeto está.

Principais Pontos

  • O Octane compila componentes no estilo React em operações diretas no DOM, removendo o virtual DOM do runtime entregue.
  • A identidade de um hook vem de onde ele está no código-fonte, e não da ordem em que os hooks executam, então um hook pode ficar dentro de um bloco if ou depois de um early return; loops comuns de JavaScript são a única posição que o compilador rejeita.
  • Você pode omitir o array de dependências e deixar o compilador ler a closure por você. Se você escrevê-lo, ele significa exatamente o que significa no React; passe null para executar em cada render.
  • Você precisa do Node.js 22.22.2 ou mais recente para os pacotes publicados, e npm create octane my-app cria a estrutura de um projeto.
  • O Octane se define como software beta: o runtime, o compilador e os caminhos de SSR/hidratação funcionam, mas as APIs ainda estão em movimento.

O Que É o Octane, e Quem o Construiu?

O Octane se posiciona como o sucessor do Inferno: a API do React que você já conhece, com o compilador assumindo três tarefas que o runtime do React faz hoje, a saber o virtual DOM, a ordenação de hooks e os arrays de dependências. Gannaway construiu o Inferno, e seus outros trabalhos incluem React, Lexical, Ripple e Svelte.

Essa linhagem é a razão pela qual isso merece dez minutos em vez de um bookmark. O Inferno buscava velocidade tornando uma implementação de virtual DOM tão rápida quanto razoavelmente possível, que é a tese examinada em nossa análise anterior do Inferno.js. O Octane mantém a meta de performance em primeiro lugar e inverte o mecanismo: em vez de um diff mais rápido, nenhum diff. O enquadramento como sucessor vem dos próprios materiais do Octane; não há anúncio correspondente do lado do Inferno.

A Ideia do Compilador: Sem Virtual DOM em Runtime

Enquanto o React constrói uma árvore de descrições de elementos a cada render e a reconcilia com a árvore anterior, o Octane compila cada template em um nó DOM que é clonado em runtime e alterado diretamente. O site de documentação do projeto é ele mesmo construído com Octane, o que é um sinal razoável de que o compilador dá conta de uma aplicação não trivial.

A consequência prática é que o trabalho que o React faz em runtime (percorrer uma árvore, comparar props, decidir o que mudou) passa a ser decidido em tempo de build. O projeto publica uma grade de benchmarks em sua página inicial, normalizada em relação ao Octane em várias suítes. São números do próprio projeto, medidos pelo próprio projeto, e a página não publica hardware nem data de execução, então trate-os como uma afirmação a ser verificada, e não como um resultado independente.

Hooks Rastreados por Call Site, Não por Ordem de Chamada

O Octane dá a cada hook sua identidade a partir do lugar em que ele aparece no código-fonte em vez da ordem em que os hooks executam, e deduz listas de dependências omitidas de efeitos e memos a partir da closure. É por isso que um hook atrás de uma condição funciona sem problemas. Essa é a mudança com maior consequência no dia a dia.

Veja o formato que você escreve no React, onde o hook precisa executar 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)} />;
}

O estado e o efeito são elevados acima do branch que precisa deles, e a lógica do branch é repetida dentro do efeito. No Octane, o hook fica onde pertence:

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

O que isso elimina na prática: o componente filho extraído apenas para tornar um hook condicional, o padrão “o hook sempre executa e condicionalmente não faz nada” e os ternários que existem para manter estável a contagem de chamadas. Note que os eventos do Octane vêm direto do DOM, então use onInput quando quiser uma atualização a cada tecla pressionada, enquanto onChange dispara quando o navegador confirma a edição.

A única restrição que o projeto aponta são os loops comuns de JavaScript. Hooks são identificados pelo call site atribuído pelo compilador, então um hook vinculado a um slot dentro de um loop for não tem identidade estável e o compilador o rejeita. Uma lista com keys no template ou um componente filho por item é o caminho.

Por Que os Arrays de Dependências São Opcionais no Octane?

Omita a lista e o compilador a deduz a partir da closure. Escreva o array você mesmo e ele se comporta exatamente como no React; passe null quando quiser que o trabalho execute em cada render. Isso vale para useEffect, useMemo, useCallback e os outros hooks que recebem uma 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);
});

A válvula de escape importa tanto quanto a inferência: um array explícito nunca é reescrito, então onde você quiser controle exato, escreva-o. Chamadas diretas aos hooks nativos preservam essa inferência em qualquer módulo processado pelo compilador, incluindo hooks customizados em arquivos .ts ou .js simples. Chamadas a um wrapper seu são um caso mais restrito: o wrapper precisa ser declarado localmente em um módulo .tsrx ou .tsx totalmente compilado, e precisa repassar seu callback e seu último parâmetro de dependências diretamente para um hook suportado.

Como Colocar o octanejs Para Funcionar?

Você precisa do Node.js 22.22.2 ou mais recente para os pacotes publicados. O comando octane create aceita --template spa para um app apenas client-side ou --template fullstack para roteamento, SSR com streaming, hidratação e build de produção; omita a flag e ele pergunta.

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

Qualquer gerenciador de pacotes com que você execute o comando é o que vai instalar as dependências, porque um diretório recém-criado não tem lockfile para ler — e é por isso que a documentação e o repositório mostram gerenciadores diferentes para o mesmo passo. Para um projeto que você já tem, o guia de início rápido cobre o caminho com Vite: instale octane e @octanejs/vite-plugin e então adicione o plugin. Esse plugin já traz o compilador consigo. Rspack usa @octanejs/rspack-plugin e Rsbuild usa @octanejs/rsbuild-plugin.

TSRX, Brevemente

TSRX é a sintaxe em que os componentes Octane são escritos, contida em arquivos .tsrx, que adiciona diretivas de template (@if, @for, @switch, @try) e blocos <style> com escopo ao lado da marcação a que se aplicam. É um projeto de linguagem por direito próprio em vez de um recurso do Octane, e o Octane é um de seus targets de compilação ao lado de React, Preact, Solid, Vue e Ripple. Ele também adiciona @{ ... }, um atalho para um corpo de função que retorna um único elemento JSX ou fragmento, com o setup no topo e o nó final como saída. Você não é obrigado a adotá-lo: o guia TSRX vs TSX/JSX destaca que os dois dialetos compartilham os mesmos hooks, context, portals, Suspense, transitions, eventos nativos, estilos com escopo, renderização no servidor e hidratação, e a recomendação do próprio guia é deixar o TSX que funciona como está, em vez de trocar extensões sem motivo.

Onde o Octane Realmente Está?

O projeto chama o Octane de software beta: o runtime, o compilador e os caminhos de SSR/hidratação todos funcionam, mas as APIs ainda podem mudar antes da 1.0. O changelog do Octane coloca os lançamentos atuais na linha 0.3, e o conselho do guia de início rápido de fixar versões em qualquer coisa séria decorre disso. Pela contagem do próprio projeto, a suíte central executa mais de 3.900 testes comportamentais separados, distribuídos entre verificações de conformidade, diferenciais, hidratação, runtime, compilador e SSR. Quanto da cobertura do próprio React isso representa é acompanhado caso a caso em um relatório de paridade gerado automaticamente, e não deduzido do total da suíte.

A interoperação funciona nos dois sentidos. ReactCompat e OctaneCompat vêm ambos do entry point octane/react: o primeiro mantém componentes React reais funcionando dentro do Octane, o segundo insere componentes Octane compilados em um app React. O guia de compatibilidade com React percorre a configuração de ambos os compiladores, a renderização de uma ilha Octane dentro de uma árvore React, o compartilhamento de context do React através da fronteira e a renderização no servidor com hidratação.

Seja realista quanto ao ecossistema. O Octane publica ports first-party @octanejs/* de bibliotecas React amplamente usadas, mas o grau de completude de cada um varia: alguns correspondem ao comportamento upstream, outros são rotulados como parciais ou alpha, e a tabela gerada em docs/bindings-status.md é onde você verifica o que um pacote cobre, qual versão upstream ele acompanha, onde divergem e se SSR e hidratação são tratados. Um conjunto curado de bindings first-party é uma coisa bem diferente do ecossistema de pacotes de que um app React se serve sem pensar duas vezes.

O Octane é a resposta mais interessante até agora para a pergunta de como fica o modelo de programação do React com o reconciliador de runtime removido, e os dois ganhos de ergonomia são reais o bastante para serem sentidos em uma tarde. Leia a tabela de status dos bindings para qualquer coisa de que você dependa, depois crie uma SPA descartável e coloque um hook dentro de um branch.

Perguntas Frequentes

Posso adotar o Octane dentro de um app React existente sem reescrevê-lo?

Sim. O entry point octane/react exporta OctaneCompat, que dá a uma subárvore Octane compilada um lugar dentro de uma árvore React 19 real, então você migra uma tela, widget ou componente por vez. Dentro da ilha, use() ou useContext leem os contexts do React ao redor, os eventos permanecem nativos e a renderização no servidor funciona importando o host de octane/react/server.

Context.Provider ainda funciona no Octane?

Não. O release 0.3.0 removeu o alias legado Context.Provider dos contexts de client, server e native, e o compilador agora rejeita acessos a Provider reconhecidos estaticamente e informa o que escrever no lugar. Use o próprio context como componente provider e passe uma prop value para ele, ou chame createElement(Theme, { value }, children). O Consumer com render prop também desapareceu, e a página Differences from React do Octane afirma que ele não será adicionado: hooks vinculados a slots permitem que use() ou useContext executem atrás de uma condição, que é justamente o problema para o qual o Consumer existia.

Algum hook está isento da regra do Octane de não usar hooks em loops?

Sim. use() e useContext não ocupam slot de hook, então são seguros dentro de um loop comum de JavaScript. Todo hook vinculado a slot, em contrapartida, compartilharia um único call site em todas as iterações, o que o compilador reporta como erro. As formas documentadas de contornar isso são a diretiva @for com keys, que dá a cada item seu próprio estado de hook, ou mover o hook para dentro de um componente filho.

Por que onChange se comporta de forma diferente no Octane e no React?

O Octane usa eventos DOM delegados reais, sem camada de eventos sintéticos, então onChange é o próprio evento change do navegador: ele dispara quando a edição é confirmada, normalmente no blur, e não a cada tecla pressionada. Use onInput para atualizações a cada edição. Inputs controlados continuam seguindo as regras do React para value e checked, e refs são props comuns em vez de algo passado por meio de um 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.