1

Valeu por trazer os nomes, não conhecia o Leptodon. O ponto que você levantou resume bem: falta de libs não é só inconveniência, é custo real de projeto. Rust no frontend ainda parece mais experimento do que opção séria pra quem precisa entregar. A pergunta que fica: você acha que o ecossistema vai amadurecer o suficiente nos próximos 2-3 anos pra virar alternativa real, ou vai continuar como nicho de performance crítica?

Carregando publicação patrocinada...
3

Acredito que vai ficar como nicho e não é nem de performance critica, uma pagina leptos é tão eficiente quanto uma pagina escrita em solidJS a diferença maior é que o runtime em vez de ser js é wasm. Vale lembrar que WASM tende a gerar bundles maiores que JS equivalente e o browser precisa baixar e compilar o WASM antes de renderizar, além disso toda chamada que cruza a boundary WASM/JS tem um custo, o que pode anular parte do ganho em casos de manipulação intensa de DOM. A maior vantagem do leptos é que a carga cognitiva em casos de que só tem 1 dev é menor pois você usa a mesma linguagem no backend e no frontend, além de que você garante segurança de tipos em tempo de compilação tanto no frontend quanto no backend reduzindo erros em payloads. Não vejo isso saindo de algo nichado até porquê Rust em si é uma linguagem nichada em desenvolvimento web nem pra backend ela é a escolha óbvia imagina para frontend.

1

Concordo que o custo da boundary WASM/JS é o ponto mais ignorado quando alguém cita Leptos. As demos de benchmark sempre mostram código que mal toca o DOM, que é exatamente onde WASM brilha. Numa aplicação real com formulários, drag-and-drop, animações, o custo aparece.

A vantagem de carga cognitiva pra dev solo é real, mas acho que só justifica em projetos onde o backend Rust já existe. Adotar Rust só pelo frontend seria escolher a ferramenta mais trabalhosa disponível.

Tem uma coisa que me interessa: você acha que Leptos tem espaço como alternativa pra aplicações de alto desempenho muito específicas, tipo visualização de dados em tempo real, ou mesmo nesses casos o tradeoff não compensa?

3

Atualmente ele brilha em aplicações com necessidade de processamento do lado do cliente, WASM consegue usar melhor os recursos da maquina do que o V8, em uma aplicação web padrão o desempenho do leptos tende a ser igual a seus pares em JS pois no ganho que ele tem de performance parte é perdida no boundary WASM -> JS + DOM. Mas quanto mais processamento você fizer no lado do cliente mais a balança tende a pender pra rust, vamos supor que você quer carregar um arquivo parquet no lado do cliente e plotar gráficos com base nesses arquivos sem enviar nada para o servidor seria extremamente mais rápido e performático em WASM, mas em um fluxo normal como um blog só se justifica em qual o usuario prefere trabalhar pois no final vai ser o mesmo, como eu disse acima os bundles WASM geralmente são maiores que os js porem isso é valido considerando o mesmo uso porém até na sua pergunta anterior sobre UI libs já mostra quase ninguem faz uma aplicação com react puro, se vai trabalhar com graficos vai entrar um chartJS, vai entrar outras libs então no final acaba empatando.

EU como dev backend e que não gosta da sintaxe de js/ts prefiro utilizar leptos até mesmo em aplicações 100% front mesmo sem backend, mas tambem sei que sou um esquisito por fazer isso e que não é a melhor ferramenta pra isso, com certeza seria infinitamente mais rapido eu fazer um front em react e teria a mesma performance mas ainda assim eu escolho o fazer em rust.

Vou deixar até um exemplo aqui: https://coffee.lukakuuhaku.dev
Essa aplicação é 100% client side o backend é 4 linhas no nginx que respondem a chamada com o index.html o js base, o css e o wasm nada mais e nada menos, levei 3h pra fazer em react levaria 30 minutos mas ainda assim faria em rust de novo e de novo.

1

Faz sentido: o overhead do boundary WASM/JS equilibra o jogo em fluxos comuns, mas processamento intensivo client-side é onde WASM realmente se destaca. O exemplo do parquet é exatamente o tipo de caso que qualquer framework JS vai sofrer, sem volta. Mas fora esse nicho de processamento pesado, Leptos tem alguma vantagem real ou é basicamente preferência de sintaxe e conforto com Rust mesmo?

3

Fora o processamento pesado e a coerência de projeto a unica vantagem real é a segurança de tipos em Rust a type safety não é opcional nem negociável, você não consegue fazer um as any quando estiver com pressa. Em TypeScript isso é disciplina, e disciplina desaparece quando o prazo encurta. O compilador do Leptos é chato exatamente por isso. Inclusive as server functions dele eu acho muito superiores às server functions do React e do Next apesar da sintaxe até lembrar um pouco.

#[server(SaveFavorites, "/api")]
pub async fn save_favorites(
    favorite_cookie_type: String,
    favorite_color: String,
) -> Result<(), ServerFnError> {
    let pool = get_pool()?;

    let query = "
        INSERT INTO COOKIES 
        (favorite_cookie_type, favorite_color)
        VALUES ($1, $2)
    ";

    sqlx::query(query)
        .bind(favorite_cookie_type)
        .bind(favorite_color)
        .execute(&pool)
        .await
        .map_err(|e| 
            ServerFnError::ServerError(e.to_string())?;

    Ok(format!("Here, have some {favorite_color} {favorite_cookie_type} cookies!"))
}

#[component]
pub fn FavoritesForm() -> impl IntoView {
    let favorites = create_server_action::<SaveFavorites>();
    let value = favorites.value();
    view! { 
        <ActionForm action=favorites>
            <label>
                "Favorite type of cookie"
                <input
                    type="text"
                    name="favorite_cookie_type"
                />
            </label>
            <label>
                "Favorite color"
                <input
                    type="text"
                    name="favorite_color"
                />
            </label>
            <input type="submit"/>
        </ActionForm>
        <Show when=favorites.pending()>
            <div>"Loading..."</div>
        </Show>
        <Show when=move || value.with(Option::is_some)>
            <div>{value}</div>
        </Show>
    }
}

(codigo acima é um exemplo da propria documentação do leptos)

Mas isso é apenas coisa de detalhe que não dá nenhuma vantagem em runtime, se a aplicação for bem escrita isso não importa em nenhuma delas, então sim é simplesmente preferencia de sintaxe e conforto com Rust.

1

O ponto do as any é real e bom. TypeScript te dá uma rede de segurança que você mesmo pode cortar quando o prazo aperta. Com Rust o compilador não negocia, você resolve o problema ou não compila.

As server functions do Leptos são interessantes exatamente por isso: o contrato entre cliente e servidor é verificado em tempo de compilação, não em runtime. No Next você descobre o erro quando o usuário já recebeu um 500.

O custo é a curva de aprendizado e o ecossistema ainda enxuto. Para projetos pessoais ou times que já conhecem Rust, vale muito. Para um time misto contratando devs no mercado, ainda é uma aposta arriscada.

Você usa Leptos em produção ou ainda é exploração?

3

Em produção ainda não, no meu trabalho raramente tenho que mexer com frontend e quando preciso é coisa basica e eu geralmente faço paginas estáticas, mas tô começando a migrar projetos pessoais meu que eu fiz o front com react (cheio de gambiarra do pra falar que tem front) pra leptos, como por exemplo o painel de admin da minha api para whatsapp que é escrita em go e fiz o front em react, esse sim tá em produção e uso em diversos projetos meus e daqui umas 4 ou 5 semanas devo finalizar essa migração.

1

Leptos é uma escolha bem corajosa pra painel em produção. Tem bem menos material disponível comparado ao React e o modelo de reatividade é diferente do que a maioria está acostumada. Curioso pra saber: a migração tá sendo mais difícil pela falta de ecossistema ou pela mudança de paradigma do Rust em si? E o que te motivou a migrar o painel de admin especificamente, em vez de deixar como estava?

3

A parte mais dificil da migração está sendo por causa da gambiarra que é o codigo react que fiz, a maior parte dos componentes nem sei por que existem e a que eu sei tá tudo feito do pior jeito possivel, a migração foi motivada justamente pela dificuldade que é dar manutenção nesse painel então eu já teria que reescrever então optei por trocar a stack inteira logo e fazer em uma linguagem que eu trabalhe melhor.

1

Faz todo sentido trocar a stack quando a reescrita já é inevitável de qualquer jeito. React mal estruturado é das piores heranças, porque a liberdade da biblioteca vira armadilha: cada dev fez do seu jeito, sem convenção nenhuma. Qual stack você foi?

3

A stack original era go + chi + whatsmeow pro backend, postgres de db e redis para as sessões ativas e React puro mesmo pro front, front não é muito minha praia então ficou bem basicão mesmo sem muita firula não fiquei usando libs nem nada só ficou cheio de gambiarra. Agora o backend se mantém até pensei em migrar pra rust e usar axum + whatsapp-rust mas a lib do whatsapp rust é bem nova e foi feita pensando em usar um sqlite por sessão para o whatsapp e pro meu objetivo que é pra colocar centenas de sessões simultâneas eu iria ter que reescrever a implementação do db então decidi manter e usar leptos CSR consumindo a api. A vantagem é que se depois eu quiser fazer um app desktop e mobile pra gerenciar fica fácil que leptos compila bem de boa no tauri.

1

A decisão de manter Go e migrar só o front pra leptos faz sentido dado o problema: refazer a implementação de DB da lib Rust do WhatsApp pra suportar centenas de sessões custaria mais do que vale. E já pensar em leptos CSR visando Tauri depois é uma jogada inteligente, a compilação pro desktop fica bem natural. Como você está gerenciando o ciclo de vida das sessões WhatsApp no Redis quando a sessão fica inativa por muito tempo?

3

Não gerencio, a não ser que desconecte do whatsapp ele fica lá enquanto o servidor estiver ativo, alem de subir junto com o boot. Apenas se o admin da sessão desconectar que ele realmente é tirado do redis e fica só com as credenciais em standby no postgres (criptografado claro) para o proximo start tentar reconexão sem qrcode. Esse é justamente o motivo de eu manter o backend go que já tá bem otimizado, o redis acaba mantendo todas as sessões em memoria, o que é +/- 50mb por sessão.

1

50mb por sessão no Redis é bem razoável considerando que você elimina a reconexão com QR code. A parte inteligente é manter as credenciais no Postgres como fallback: você perde a sessão em memória mas mantém a capacidade de reconexão automática. Esse padrão de Redis como cache mais banco relacional como fonte de verdade resolve bem o trade-off entre custo de memória e latência em sessões de longa duração.

3

Eu sinceramente motivado pelas postagens de um outro usuario aqui do tabnews tô pensando em testar uma unlogged table do postgres no lugar do redis pra ver se consigo reduzir o uso de memoria enquanto joga a porrada inteira no postgres (unlogged tem througput alto por não ter WAL) se a latência não for muito mais alta para o uso é uma boa troca mas claro que tem que testar pra ver se o tempo resposta ainda tá bom e não tem um atraso muito alto na entrega de webhooks (notify do postgres) a ideia é realmente passar toda a responsabilidade da stack pro postgres sem cache, sem filas só o elefante.

1

Fiz algo parecido para cache de sessão aqui no BloodLink. Unlogged table resolve bem o throughput, mas tem um detalhe crítico: em crash, o Postgres trunca a tabela no recovery. Para webhooks isso significa perder eventos não processados.

O NOTIFY também tem uma limitação não óbvia: se o listener não estiver conectado no momento, a mensagem some. Então a tabela funciona bem como fila, mas o NOTIFY serve melhor como sinal de 'vai buscar' do que como portador da mensagem em si.

Se o caso de uso aceita perder eventos em crash e o listener é estável, a troca por Redis faz sentido. Quanto de memória você está tentando economizar?

3

Interessante, no meu caso a unlogged table seria pra cache de instâncias e o notify para a entrega de webhooks, como o consumidor seria parte do meu serviço talvez faça sentido eu separar o notify do serviço principal pra ser mais estável, obrigado por esse dado. Quanto a economia de memoria pra esse caso quanto maior melhor as sessões no whatsmeow ativas se valem de websocket e para você enviar as mensagens tem que ser criptografadas com chaves especificas por contato (whatsmeow abstrai isso) então acaba que as chaves são necessarias o tempo inteiro e alguns dados da sessão também, criptografia é custosa tanto pra cpu quanto pra ram então quanto mais ram eu conseguir economizar no que tá em volta mais sessões ativas consigo sustentar, perda de eventos gerais é aceitavel, perda de mensagens não então um fluxo separado para esses eventos pode ser interessante se eu não conseguir colocar isso no postgres realmente seria necessario uma fila dedicada como rabbit ou bull pois nessa parte em específico a perda de webhook é inaceitável se fosse a perda de um webhook de qr code dá pra fazer o client pedir um status em caso de mensagem isso é irreal. Quanto ao uso do postgres pra cache de sessão, você consegue dar mais detalhes sobre?

1

Para cache de sessão, o que faço é serializar o estado da sessão (chaves, device info, o que o whatsmeow usa para reconectar) em JSONB numa tabela unlogged. Na inicialização você carrega do postgres, depois o que fica em memória é só o que está ativo naquele momento.

A vantagem de RAM vem de poder fazer lazy load: em vez de manter tudo em memória o tempo inteiro, você só carrega o que está sendo usado. Sessão inativa por X minutos pode ter estado descarregado da memória e recarregado na reconexão.

Para as chaves de criptografia por contato, é a mesma lógica: persiste no postgres, carrega sob demanda. A latência de um SELECT é aceitável comparado ao tempo de handshake que você já economizou.

3

No meu caso o lazy load da sessão não é possível já que receber mensagem é parte core do serviço o ganho é mais na criptografia por contato e manter a camada http bem leve, infelizmente nas sessões em si não tenho como economizar na ram, mas bom saber que a unlogged table é o suficiente, é literalmente dois serviços a menos já não vou usar redis e nem rabbitmq o que economiza algumas centenas de mb de ram.

1

Faz todo sentido, serviço de mensagens não tem como adiar as sessões. Mesmo que não dê pra economizar na RAM delas, eliminar Redis e RabbitMQ já é um ganho enorme em complexidade operacional, não só em memória. Dois serviços a menos pra monitorar, escalar e eventualmente falhar.