Dioxus 0.5: apps web, desktop e mobile desenvolvidos em Rust
(dioxuslabs.com)- O framework de GUI em Rust Dioxus 0.5 simplifica bastante os fluxos de desenvolvimento web, desktop, mobile e Fullstack, com foco na reescrita do
dioxus-coree na remoção de unsafe - Entre as versões 0.4.3 e 0.5.0, foram incluídas mais de 100.000 linhas de código e mais de 1.400 commits; o novo core mudou para eliminar dependências de lifetimes e de
Scope - O gerenciamento de estado antes centrado em
use_stateeuse_reffoi substituído pela APISignalcopiável, reduzindo o custo deClonerepetitivo em event handlers e futures - Com um único
dioxus::launche o fluxodx serve --platform, é possível executar apps web, desktop e Fullstack; a CLI passa automaticamente as build features adequadas à plataforma de destino - Hot reload de assets, reescrita de eventos, melhorias na renderização desktop e streaming de funções de servidor também foram alterados, ampliando o alcance de uma base de código única em Rust
Direção do lançamento do Dioxus 0.5
- Dioxus é uma biblioteca para criar GUIs em Rust e é usada para distribuir apps web, desktop e mobile
- O lançamento 0.5 foi projetado com o objetivo de melhorar a simplificação, a robustez e o nível de acabamento pedidos pela comunidade
- As principais mudanças são as seguintes
- Reescrita completa do
dioxus-coree remoção de código unsafe - Introdução de uma API baseada em
Signalno lugar deuse_stateeuse_ref - Remoção de todos os lifetimes e do estado
cx: Scope - Uma única função
launchpara iniciar apps em todas as plataformas - Hot reload de assets com suporte a Tailwind e Vanilla CSS
- Reescrita de eventos que permite acessar tipos de evento nativos do
WebSys - Integração de Error Boundary, Server Future e Suspense
- Melhoria de 5 vezes no reconciliation em desktop
- Streaming de funções de servidor e hot reload Fullstack
- Reescrita completa do
- Há um migration guide disponível para usuários que estão atualizando a partir do Dioxus 0.4
Remoção de lifetimes e Scope
- Do Dioxus 0.1 ao 0.4, valores dentro de componentes tinham o lifetime
'bump, permitindo usar hooks, props e scope em event listeners sem clonagem - Em event handlers isso geralmente funcionava bem, mas futures do Dioxus precisam ser
'static, então era necessário fazercloneantes de mover valores para dentro de uma future - Quando ocorria um erro de lifetime, a mensagem podia dizer que
cx, e não o hook em si, precisava viver mais que'static, o que podia gerar confusão - O Dioxus 0.5 remove
Scopee o lifetime'bump, e transformaElementem uma forma sem lifetime - Componentes agora podem receber props diretamente, sem um parâmetro de scope
- Ex.:
fn MyComponent(name: String) -> Element
- Ex.:
- Funções de runtime podem ser usadas diretamente não só dentro de componentes, mas também dentro de futures e event handlers
- Como
Elementpassou a ser'static, também é possível usá-lo dentro de hooks ou fornecê-lo por meio da context API - Essa mudança cria uma base que facilita implementar APIs como listas virtuais ou renderização off-screen
Remoção de unsafe do dioxus-core
- A remoção do lifetime
'bumpe de scope criou uma oportunidade para reduzir código unsafe dentro do Dioxus dioxus-core 0.5não contém código unsafe- Algumas dependências ainda têm uma pequena quantidade de unsafe, e a equipe do Dioxus planeja removê-la durante o ciclo de lançamento da 0.5
- O unsafe restante é classificado como algo que pode simplesmente ser removido ou como necessário por causa de FFI
Gerenciamento de estado baseado em Signal
- O Dioxus 0.5 introduz Signal como tipo primitivo central de estado para componentes
- Signal tem duas vantagens em relação aos antigos
use_stateeuse_ref- É sempre
Copy - Não exige assinatura manual
- É sempre
-
Estado Copy
Signal<T>éCopymesmo quando o valor internoTnão éCopy- Esse comportamento é possível graças ao crate generational-box, implementado sem unsafe
- Se necessário, Signal pode ser
Send+Sync, permitindo movê-lo entre threads - A combinação de estado Copy, Signal
Send+Synce componentes static permite mover estado facilmente para onde for necessário, como futures, event handlers e threads - O padrão de uso de memória é quase igual ao da 0.4, mas sem necessidade de
Cloneexplícito
-
Assinaturas inteligentes
- Signal decide com mais granularidade quais componentes devem ser executados novamente quando o valor muda
- Um componente só é executado novamente se tiver lido o valor do Signal
- Leituras em tarefas async ou event handlers não são tratadas como assinaturas para reexecutar o componente
- Se um pai altera um Signal ao clicar em um botão, mas não lê o valor diretamente, e apenas o filho lê o valor, só o filho é renderizado novamente
- Graças a essa estrutura, o Fermi, que era um crate separado de gerenciamento de estado, deixa de ser necessário
- Fermi fornecia uma API semelhante a
use_stateusando static como chave - No Dioxus 0.5, é possível colocar
GlobalSignalem um static e usá-lo como um Signal comum - Signal também funciona com a context API, permitindo compartilhar estado entre componentes sem um hook
use_shared_stateseparado - Ao ler um Signal dentro de hooks como
use_futureeuse_memo, esse Signal é adicionado automaticamente às dependências do hook
Hot reload de CSS e assets
- Como parte da reformulação do sistema de assets, o Dioxus 0.5 implementa hot reload para arquivos CSS no diretório de assets
- Quando um arquivo CSS aparece dentro de RSX, a CLI
dxmonitora esse arquivo e transmite imediatamente as atualizações para o app em execução - O suporte abrange Web, Desktop e Fullstack; suporte mobile será oferecido em uma futura atualização com foco em mobile
- Usado junto com o watcher do Tailwind, também há suporte a hot reload de Tailwind CSS
- No VSCode, é possível obter dicas de classes Tailwind por meio da custom regex extension
- É possível transmitir mudanças simultaneamente para vários dispositivos e fazer hot reload em todos os dispositivos de destino
Reescrita do sistema de eventos
- Desde seu lançamento, o Dioxus usa um sistema de synthetic events para criar uma API de eventos cross-platform
- Synthetic events são úteis para comportamento de eventos entre plataformas e serialização em rede, mas também têm limitações
- O Dioxus 0.5 expõe os tipos de evento subjacentes de cada plataforma e também fornece traits para a API cross-platform
- Essa mudança traz duas vantagens
- É possível obter diretamente as informações necessárias a partir do tipo de evento da plataforma ou passá-lo para outras bibliotecas
- Código de eventos que o app não usa pode ser separado por bundle splitting
- No exemplo hello world, o tamanho gzipped caiu cerca de 25%
- Dicas para criar bundles pequenos estão incluídas no Dioxus optimization guide
API de execução cross-platform
- O Dioxus 0.5 introduz uma nova API cross-platform para executar apps
- Em vez de importar um pacote de renderer separado, basta ativar a feature no crate
dioxuse chamar a funçãolaunchdo prelude - Um único app pode ser executado tendo como alvo as seguintes plataformas
- Desktop:
dx serve --platform desktop - SPA Web:
dx serve --platform web - Fullstack:
dx serve --platform fullstack
- Desktop:
- A CLI passa automaticamente a build feature apropriada de acordo com a plataforma de destino
Sistema de assets beta e Manganis
- Em Dioxus e em apps web, caminhos de assets podem ficar desatualizados, links podem variar entre desktop e web, e assets a incluir no bundle precisam ser adicionados manualmente
- Assets também podem se tornar gargalos de desempenho
- No exemplo do guia Dioxus Mobile, a versão 0.4 levava 7 segundos para carregar e transferia 9 MB de recursos
- O guia mobile da 0.5 usa as mesmas imagens, mas carrega em menos de 1 segundo e reduz os recursos necessários para 1/3
- O Dioxus 0.5 introduz o novo sistema de assets manganis
- Ele se integra à CLI para verificar, empacotar e otimizar assets do app
- A API ainda é instável, então é distribuída como um crate separado
- Ao envolver assets com a macro
mg!, a CLI os detecta automaticamente - Mais detalhes estão na documentação do manganis
- Durante o lançamento da 0.5, também há planos de adicionar hot reload aos assets do manganis
Renderização desktop 5 vezes melhor
- O Dioxus usa várias otimizações para tornar o diff de renderização rápido
- Templates permitem pular o diff das partes estáticas da macro
rsx! - No Dioxus Web, o sledgehammer aplica rapidamente mudanças no DOM a partir de Rust
- O Dioxus 0.5 usa a mesma abordagem para aplicar mudanças via rede
- Os renderers Desktop e LiveView comunicam mudanças por meio de um protocolo binário em vez de JSON
- Em trabalhos com alta carga de renderização, o novo renderer reduz para 1/5 o tempo para aplicar mudanças no navegador e reduz a latência pela metade
- Um benchmark em que o renderer travava continuamente no Dioxus 0.4 roda suavemente no Dioxus 0.5
Recursos que facilitam escrever componentes
- O Dioxus 0.5 oferece suporte a estender um element específico e espalhar atributos em um element
- Ex.: em um componente
ImgPlusque estende os atributos de um elementimg, é possível receber normalmente atributos comuns deimg, comowidth,heightesrc
- Ex.: em um componente
- Ao passar valores para atributos e componentes, é possível usar a sintaxe abreviada de inicialização de structs
- É possível escrever
classem vez declass: class
- É possível escrever
- Atributos abreviados funcionam com qualquer item que implemente
IntoAttribute, e Signal também se beneficia disso - Atributos Signal ainda não pulam diffing, mas isso deve ser adicionado como otimização de desempenho durante o ciclo de lançamento da 0.5
- Atributos divididos em várias linhas podem ser mesclados
- Ao adicionar valores condicionais ao mesmo atributo
class, eles são mesclados usando espaço como separador - Isso é importante para bibliotecas como Tailwind, que exigem parsing em tempo de compilação e também processamento dinâmico em runtime
- Essa sintaxe é integrada ao compilador do Tailwind e elimina o overhead em runtime de bibliotecas como
tailwind-merge
- Ao adicionar valores condicionais ao mesmo atributo
Streaming de funções de servidor e Fullstack
- O Dioxus 0.5 oferece suporte ao crate moderno de server functions, que dá suporte a dados em streaming
- Funções de servidor podem transmitir dados para o cliente ou transmitir dados do cliente para o servidor
- Uma função de servidor em streaming pode ser criada definindo o tipo de saída e retornando
TextStreama partir da função de servidor - Isso é adequado para atualizar o cliente durante tarefas demoradas
- Há um exemplo que usa Kalosm e um LLM local para fornecer, em hardware comum, uma funcionalidade semelhante ao endpoint OpenAI ChatGPT
- Repositório de exemplo: https://github.com/ealmloff/dioxus-streaming-llm
- A CLI agora oferece suporte à plataforma
fullstacke fornece hot reload e builds paralelos para cliente e servidordx servedx serve --platform fullstack
LiveView, handler de assets e processamento de arquivos
- No Dioxus 0.5, o router funciona por padrão em apps LiveView
- PR relacionado: https://github.com/DioxusLabs/dioxus/pull/1505
- O Dioxus Desktop oferece suporte a custom asset handler
- PR relacionado: https://github.com/DioxusLabs/dioxus/pull/1719
- Custom asset handlers permitem transmitir dados de forma eficiente do código Rust para o navegador sem passar por JavaScript
- Isso é adequado para comunicação de alta largura de banda, como streaming de vídeo
- PR relacionado: https://github.com/DioxusLabs/dioxus/pull/1727
- É possível entregar dados de gstreamer ou webrtc diretamente para a webview, reduzindo a necessidade de codificar e decodificar frames manualmente
- O drop de arquivos no desktop também foi integrado nativamente ao sistema de eventos
Tratamento de erros
- Dioxus permite tratar erros facilmente em componentes superiores por meio de Error Boundary e do trait
throw - A abordagem
throwcombina as vantagens de estado de erro e retorno antecipado - É possível chamar
throwem um tipoResultque implementaDebugpara transformá-lo em estado de erro e retornar antecipadamente com? - O componente
ErrorBoundaryrenderiza outro componente quando há um erro lançado por um filho ErrorBoundarypode ser aninhado, permitindo capturar erros em vários níveis do app- Esse padrão é útil para tratar estado de erro global quando ocorre um erro irrecuperável, sem recorrer a panic nem gerenciar manualmente o estado para cada erro
Experiência de desenvolvimento e templates
- Dioxus introduziu hot reload na 0.3, adicionou isso ao Desktop na 0.4 e, na 0.5, ativou por padrão
- Ao executar um app com
dx serve, o hot reload fica ativado por padrão no modo de desenvolvimento - Mesmo quando hot reload não é possível em apps Desktop e é preciso recompilar tudo, o estado da janela aberta é preservado e restaurado
- O tamanho e a posição da janela do app são mantidos
- Isso reduz situações em que o app cobre a tela inteira a cada edição
- Os novos templates foram organizados para permitir criar apps Web, Desktop, Mobile, TUI e Fullstack com um único comando
- O app padrão de
dx newpassou a ter uma forma mais próxima de create-react-app- Inclui assets, CSS e configuração básica de deploy
- Inclui links para recursos úteis, como dioxus-std, VSCode Extension, documentação e tutoriais
Dioxus Community e ecossistema
- A Dioxus Community atualizou crates importantes do ecossistema para o lançamento 0.5
- Crates como icons, charts e a biblioteca padrão específica para Dioxus foram preparados para uso imediato no momento do lançamento da 0.5
- O projeto
Dioxus Communityé uma nova organização no GitHub para manter crates importantes atualizados mesmo quando o mantenedor original se afasta - Para quem cria bibliotecas para Dioxus, o lado do Dioxus pode ajudar na manutenção, com o objetivo de mantê-las, na prática, como suporte “Tier 2”
Recursos planejados para o futuro
- Os planos após a 0.5 incluem os seguintes itens
- Estabilização e integração mais profunda do sistema de assets
- Bundle splitting direto do
.wasmde saída com lazy component - Islands e resumable interactivity, serialização de Signal
- Fusão de Server component e LiveView em Fullstack
- Devtools aprimoradas e framework de testes
- Reformulação completa de Mobile
- Reformulação de Fullstack, incluindo WebSocket, SSE e progressive form
Prévia do Dioxus-Blitz baseado em Servo
- No “Blitz 2.0” do Dioxus-Blitz, o objetivo é integrar o Servo para renderização nativa em WGPU com o mesmo mecanismo CSS usado para rodar o Firefox
- Nico Burns, criador da biblioteca de layout Taffy, entrou em tempo integral para impulsionar esse trabalho
- Na demo,
google.comé renderizado na GPU a 900 FPS - A implementação atual ainda não está completa e a renderização de
google.comainda é um pouco estranha, mas está se aproximando rapidamente de um nível utilizável - O repositório pode ser consultado em https://github.com/jkelleyrtp/stylo-dioxus
Como contribuir
- O projeto Dioxus busca as seguintes contribuições
- Tradução da documentação
- Tentar “Good First Issues”
- Melhorias na documentação
- Contribuições para a CLI
- Responder a perguntas da comunidade no Discord
- A equipe do Dioxus agradece pelo apoio da comunidade durante o restante de 2024 e pede ajuda para transformar o desenvolvimento de apps
1 comentários
Opiniões no Hacker News
Quando comecei a trabalhar, ele estava na versão 0.2, e parece que as mudanças desta 0.5 removeram quase toda a complexidade que eu encontrei na época. Ainda não testei pessoalmente, mas a remoção de lifetimes e a redução da necessidade de ficar clonando tudo devem tornar a experiência bem mais agradável
Há muitos frameworks Rust tentando oferecer UI reativa nativa que possa ser distribuída para desktop, web/wasm, mobile etc. https://github.com/flosse/rust-web-framework-comparison, então me preocupa escolher errado e acabar tendo que manter um framework abandonado ou fazer uma migração dolorosa.
Frameworks de servidor HTTP em Rust também eram muitos, e agora Axum, Actix e Rocket parecem estar na frente; além disso, a comunidade parece estar migrando para Axum, então às vezes me pergunto se escolher Actix foi um erro. Gostaria de saber se há sinais de que o Dioxus vai vencer, ou se há indicadores de que ele é uma escolha segura hoje além de uma comunidade grande, apoio da YC e momentum.
Também gostaria de saber se Leptos e Yew são concorrentes importantes, e em que eles são melhores ou piores que o Dioxus. Minha empresa investiu bastante em Rust, Actix e Bevy, e no futuro queremos combinar frameworks como Bevy e Dioxus para criar clientes nativos para desktop e mobile.
E também fico curioso se o nome Dioxus vem do Deoxys de Pokémon. O logo dá essa impressão, mas não há referências a Pokémon no codebase
Há uns 9 meses, criei um frontend GUI para sshfs com Dioxus, e minha impressão era de que ele era um framework GUI realmente excelente até você bater em alguma parede de um recurso que os desenvolvedores ainda não tinham terminado.
Compartilhar estado entre contextos diferentes também às vezes é doloroso, mas todo framework GUI que já usei foi assim, independentemente da linguagem ou da tecnologia de base. O Dioxus 0.5 parece um grande avanço nessa área. Meu blog renderiza uma parte considerável do HTML com Dioxus e, como não é um uso que força os limites, funciona muito bem
No entanto, a solução de remoção de lifetimes me parece um pouco estranha. O generational-box me parece uma espécie de coletor de lixo de pobre, então fico curioso sobre o impacto em desempenho.
Além disso, o link
[generational-box]([https://crates.io/crates/generational-box](<https://crates.io/crates/generational-box>))está quebradouse_hookpossui o valor durante o lifetime do componente, então quando o componente desaparece, esse valor também é descartado. Todos os signals ainda usamuse_hook, portanto o lifetime também é o mesmo. Normalmente não se recomenda chamarGenerationalBox::new()fora deuse_hook, então não há impacto em desempenho.Se você sair chamando
GenerationalBox::new()sem parar dentro de um loop, esse lixo ficará lá até o componente ser descartado, mas na maior parte dos casos as pessoas vão fazer push/pop em um Map ou Vec, e aí se aplica a semântica de memória normalEntendo que seja um tipo de alocação em arena, mas não entendo como ele oferece “copiar sem copiar”, nem por que é seguro dizer que ele cria internamente uma arena de
RefCellcom gerações e queGenerationalBoxéCopy.Entendo que seja possível criar ponteiros para dados estáticos, mas fico curioso sobre o que acontece com valores que não têm lifetime estático
A referência ao dado se mantém durante o lifetime do programa. A generational box permite colocar dados que vivem menos que o programa, mas esses dados não podem conter referências. Quando os dados inseridos são descartados, a box é reutilizada para outra alocação.
É uma abordagem muito parecida com uma arena geracional, exceto pelo uso de boxes em vez de uma arena centralizada, uma escolha para evitar problemas de locking. Se você tentar acessar os dados por uma referência
Copydepois de eles terem sido descartados, a operação falha com uma boa mensagem de erroCopytem um significado específico.Quando um tipo implementa o trait
Copy, isso significa que ele pode ser copiado commemcpy; não é uma cópia profunda, é mais como uma cópia rasa.Então não entendo como “copiar sem copiar”, mas sim como permitir tratar um tipo que não é
Copycomo se fosse um tipoCopy. O README diz que conteúdo estático é necessário, então para valores sem lifetime estático a resposta é “não dá para fazer isso”Gosto do Dioxus porque ele pega muitos dos elementos que fizeram o React dar certo, mas inova e entrega rapidamente em cima disso. Estou ansioso para experimentar os signals desta versão
Reconheço as coisas boas que o React fez pelo JS e pela web, além do peso do nome, mas, se você pudesse projetar uma linguagem ou DSL do zero em 2024, não acho que código de “UI reativa” precise necessariamente se parecer com React.
Não quero dizer que SwiftUI seja perfeito, mas o código escrito em SwiftUI parece muito mais limpo, organizado e compartimentado do que código equivalente em React
A verdadeira vantagem de usar JSX em GUIs multiplataforma é mais ou menos poder reutilizar bibliotecas existentes para a Web; já o RSX parece ter pouco “valor portável” além de permitir que o desenvolvedor transfira seu conhecimento conceitual de JSX para RSX. Talvez o melhor compromisso seja aprender um novo paradigma que seja objetivamente melhor que o paradigma React/JSX
Em resumo, seria ótimo se existisse um projeto que fosse “SwiftUI, mas multiplataforma”, mas ainda não há. Conheço @Tokamak/TokamakUI, mas ele ainda parece bem incompleto e a atividade parece ter diminuído
Atualmente ele só oferece suporte a apps desktop nativos para Linux/mac/windows, mas há planos de suporte a WASM, Web e mobile
Ela será o primeiro site descentralizado que as pessoas verão ao configurar o Freenet. Ele é um pouco parecido com o Kweb, um framework Web em Kotlin no qual venho trabalhando de forma intermitente há alguns anos, especialmente no tratamento de estado e na abordagem de DSL que mapeia código para HTML. Até agora, estou gostando
https://freenet.org/
https://kweb.io/
Na verdade sou fã de Kotlin e gosto da DSL de Kotlin e do seu modelo de concorrência
O Tauri coloca o frontend em uma WebView, e você precisa se comunicar com funções Rust nativas por meio de uma fronteira de IPC, como no Electron
No Dioxus, o código Rust fica no lado nativo, então tarefas como ler o sistema de arquivos ou usar WebSockets não precisam de IPC. O Tauri força a compilação do frontend para WASM, e muitas crates Rust interessantes não compilam para wasm
É difícil expressar em palavras o quanto o desenvolvimento fica mais simples quando não há fronteira de IPC. Como as ferramentas do Dioxus têm como alvo apenas Rust, é possível ir do zero a um
.appempacotado em menos de 1 minuto. Um build novo leva cerca de 12 segundos, e um bundle novo, cerca de 20 segundosAinda assim, gosto muito da flexibilidade que o Tauri oferece: poder usar como frontend qualquer UI compatível com a Web. E também é possível usar Dioxus dentro de um app Tauri
Foi bom o Dioxus tratar disso diretamente no README, mas também fico curioso sobre a experiência real de pessoas que usaram os dois
Fiz um app desktop de brinquedo com Tauri e confirmei se o IPC era rápido o bastante para o frontend Web atualizar a cada tecla pressionada e trabalhar sem latência; a resposta foi “sim”. Se dava para enviar um arquivo grande do frontend para a camada Rust a cada tecla pressionada e recebê-lo de volta no frontend, pelo menos na minha implementação ingênua, a resposta foi “não”
Tanto Tauri quanto Dioxus têm muitos casos em que parecem bons, e em alguns casos o Dioxus deve ser melhor. Gostaria de ouvir relatos comparativos do tipo “no nosso projeto escolhemos Dioxus ou Tauri por causa de X e Y”. O Dioxus parece muito legal, estou ansioso para experimentar
O stylo é a parte do Servo compartilhada com o Firefox. No longo prazo, queremos levar as pessoas para o renderizador WGPU, mas ele ainda está em um estágio bem inicial, e muitas empresas que usam Dioxus sabem, de forma pragmática, que a WebView é uma solução boa o suficiente para cerca de 90% do app
Entendo que às vezes as pessoas usam
unsafecom facilidade demais, mas a biblioteca padrão também é cheia deunsafe, e traçar a linha ali às vezes parece desenhar uma linha na areiaEle é usado em três lugares: alguns ajustes de FFI no iOS, a implementação para permitir sintaxe de chamada de função em signal e a parte que implementa Send/Sync para IDs que usam ponteiros como hash
Agora que olho para o último caso, talvez seja possível removê-lo substituindo por
usizeunsafeMesmo assim, não é necessariamente uma decisão ruim um autor de crate estabelecer como meta remover
unsafeda própria crateAutores de crates que tentam remover todo
unsafemuitas vezes querem reduzir o ônus de confiança que o usuário precisa assumir. Para mim, esse é o poder da palavra-chaveunsafe. Na verdade, talvez ela devesse ter sido dividida entre blocostrust_mee funçõescheck_yourselfunsaferestringe a conversa sobre segurança de memória a pontos muito estreitos e auditáveis do código e, ao mesmo tempo, cria uma nova conversa sobre confiança que é gerenciáveluse*para a API de hooks, mas fico curioso por que, no Dioxus, a função que cria um novo signal se chamause_signallet mut count = use_signal(|| 0);não é uma chamada que cria um novo signal? O signal não é recriado a cada renderização, e esse é justamente o ponto de um signalNo SolidJS, você cria um signal com createSignal, como em
const [count, setCount] = createSignal(0), e isso é muito mais fácil de entenderNomes de API importam. Isso porque hooks de estado se comportam de maneira diferente e também podem surgir necessidades complementares como
useMemo. Fico curioso se há algum motivo para ter escolhido o nomeuse_signalalém de fazer o Dioxus parecer próximo do ReactEle é mais próximo do Preact do que do Solid
As otimizações incluem separar as partes dinâmicas da UI em “diff bins” separados, memoização básica e signals marcando propriedades implicitamente como dirty/managed