Pitch: No.JS: um framework reativo HTML-first (sem necessidade de JS da sua parte)
Estive trabalhando em um projeto paralelo nos últimos 5 meses, e finalmente cheguei a um ponto onde me sinto confortável em compartilhá-lo.
Ele se chama No.JS. É um framework reativo HTML-first. A ideia é simples: e se você pudesse construir web apps reativos usando apenas atributos HTML, sem escrever JavaScript?
Como tudo começou
Eu estava no meu último emprego, imerso em uma base de código Angular. Não era ruim, honestamente. Bem arquitetada, boa equipe. Mas um dia eu precisei adicionar um dropdown que filtrava uma tabela. Coisa simples, o tipo de coisa que deveria levar dez minutos, no máximo.
Eu criei um componente, um módulo para declará-lo, um service para buscar os dados, uma interface para o tipo de resposta, um pipe observável para fazer o debounce do input, e um template que referenciava tudo isso. Seis arquivos, talvez umas quarenta linhas espalhadas entre eles, apenas para dizer "quando isso mudar, busque aquilo de novo e mostre aqui."
Lembro de olhar pro meu vscode, clicando entre filter-dropdown.component.ts, filter-dropdown.module.ts, filter.service.ts, filter.model.ts, e pensando: a lógica real com a qual eu me importo cabe em uma frase. Todo o resto é apenas o framework e as convenções estabelecidas (ODEIO ELAS!) me pedindo para provar que estou falando sério ou que eu sei toda aquela ˆ%&ˆ*(.
Esse pensamento ficou na minha cabeça. Então fui dar uma olhada por aí. Encontrei um projeto incrível com um nome ainda melhor: HTMX.
HTMX foi a primeira coisa que tentei. Um projeto genuinamente excelente, e ele acerta em cheio no modelo server-driven. Se o seu backend é o cérebro, o HTMX simplesmente conecta o HTML a ele de forma linda. Mas eu não tinha um backend. Eu tinha uma página estática e uma API pública. O HTMX assume um servidor que retorna fragmentos de HTML, e para o meu caso de uso isso significava que eu ainda precisaria subir um servidor só para fazer proxy e criar templates das respostas.
Aí eu tentei o Alpine.js. Mais próximo do que eu queria. Reativo, leve, fica no HTML. Gostei muito. Mas depois de alguns dias, continuei esbarrando em paredes: sem HTTP declarativo, sem roteamento SPA, sem um padrão nativo de loops sobre dados buscados. Eu estava escrevendo pequenos scripts x-init para buscar, fazer o parse e atribuir dados, para então conectar o x-for separadamente. Funcionava, mas parecia que eu mesmo estava montando o encanamento todas as vezes, e o que eu queria (apenas apontar este elemento para um endpoint e renderizar o que voltasse) estava sempre fora de alcance.
O que me faltava era o meio-termo. Algo que viva inteiramente no HTML como o Alpine, converse com APIs como o HTMX, mas trate todo o ciclo de vida (fetch, bind, loop, route) como uma superfície contínua. Não é uma história de servidor. Não é uma história de scripting. É uma história de HTML.
Então comecei a construir um.
Como ele é
Uma caixa de pesquisa reativa no No.JS:
<div state="{ query: '' }" get="/api/search?q={{ query }}" as="results">
<input model="query" />
<li each="r in results" bind="r.name"></li>
</div>
Quatro linhas. É reativo, faz o fetch automaticamente quando a query muda, e renderiza os resultados. Sem imports, sem hooks, sem etapa de build. (Peguei isso da minha documentação html só pra mostrar a vocês como funciona)
O pensamento por trás disso
Os navegadores já entendem HTML. Eles já lidam com eventos, atualizam o DOM, gerenciam o layout. Em algum momento no meio do caminho, começamos a tratar o navegador como algo a ser contornado, em vez de algo com o qual trabalhar.
O HTMX provou que muitas pessoas sentem a mesma atração de volta para o HTML. O Alpine provou que você pode ter reatividade sem uma etapa de build. O No.JS tenta levar isso mais longe: e se os atributos HTML pudessem cobrir toda a superfície (busca de dados, estado, roteamento, validação, i18n) para que você nunca mais precise descer para um bloco de script?
Os atributos se tornam a API: bind para dados, each para loops, get para buscas, state para reatividade. Seus templates são HTML válido que qualquer navegador consegue ler.
Não é anti-JavaScript. Ainda tem JS rodando por baixo dos panos. Mas a camada voltada para o desenvolvedor é o HTML, e para muitos casos de uso, isso acaba sendo o suficiente.
O que está incluso
É mais completo do que você esperaria:
- HTTP declarativo (
get,post,put,delete) - Binding reativo (
bind,model) - Condicionais e loops (
if,show,each,switch) - Gerenciamento de estado (
statelocal,storeglobal,computed,watch) - Roteamento SPA com guards, parâmetros e rotas aninhadas
- Validação de formulários
- Animações e transições
- i18n (internacionalização) com pluralização
- Mais de 30 filtros nativos
- Diretivas customizadas
~11 KB gzipped, zero dependências.
Em que pé está
Eu reescrevi o núcleo três vezes. Eu fui e voltei na API de diretivas mais do que gostaria de admitir. Escrevi testes, escrevi a documentação e construí o site da documentação com o próprio No.JS.
Ele não vai substituir o React para projetos de grandes equipes com necessidades de ferramentas complexas. Esse não é o objetivo. Mas para landing pages, dashboards, ferramentas internas, protótipos ou qualquer coisa em que você precise apenas de algo reativo sem tanta cerimônia, ele funciona muito bem.
Uma coisa sobre a qual serei honesto
Quando a sua linguagem de template vive em atributos HTML e avalia expressões em tempo de execução, você está essencialmente entregando ao navegador um minúsculo interpretador. Isso me tira um pouco o sono. Coloquei algumas proteções em prática (avaliação em sandbox, nada de construtor Function em inputs voltados pro usuário, isolamento de escopo entre componentes), mas eu não testei isso em batalha do jeito que um framework com cinco anos e cem contribuidores testou. Superfícies de XSS, injeção de expressão, o que acontece quando alguém joga dados de API não sanitizados direto em um bind – ainda estou mapeando tudo isso.
Se você já trabalhou com políticas CSP, sanitização de templates ou sandboxing em tempo de execução e algo aqui te faz estremecer, eu genuinamente quero te ouvir. Segurança é a única área onde "funciona na minha máquina" não é bom o suficiente, e prefiro que alguém encontre as falhas agora do que descobrir da pior maneira mais tarde.
O projeto é open source (MIT): github.com/ErickXavier/no-js
Se quiser experimentar:
<script src="[https://unpkg.com/@erickxavier/no-js@latest/dist/iife/no.js](https://unpkg.com/@erickxavier/no-js@latest/dist/iife/no.js)"></script>
Esse é o setup inteiro.
A propósito, eu não queria meu nome na url do pacote npm, mas apenas no-js era parecido demais com outros 2 projetos mortos: nojs e no.js. E eu apenas segui a sugestão do NPMJS, usei meu nome (username do github).
Eu cobri o projeto com testes, mas espero que a comunidade encontre bugs e crie seus próprios PRs. Por favor, façam isso! Preciso de toda a ajuda possível com este aqui!
No geral, estou curioso para saber o que as pessoas acham. Estive focado nisso por um bom tempo e adoraria uma perspectiva externa. Feedback, perguntas, críticas, sugestões, são todos bem-vindos.