3 pontos por GN⁺ 2024-08-17 | 1 comentários | Compartilhar no WhatsApp

Exemplos de Vanilla JSX

E se JSX retornasse elementos DOM?

  • A função ClickMe cria um botão e exibe a contagem de cliques
  • O texto é atualizado cada vez que o botão é clicado
export default function ClickMe() {
  let i = 0;
  const el = Click me as HTMLButtonElement;
  el.onclick = (e) => {
    el.textContent = `Clicked ${++i} times`;
  };
  return el;
}

Reutilização

  • O componente ClickMe pode ser usado várias vezes, mantendo estados diferentes para cada instância
import ClickMe from "./sample1.js";
export default () =>

;

Criação de uma árvore DOM interativa

  • É possível gerenciar uma lista de tarefas usando as classes TodoInput e TodoList
  • Itens podem ser adicionados e removidos ao clicar neles
function TodoInput(attrs: { add: (v: string) => void }) {
  const input =  as HTMLInputElement;
  input.placeholder = 'Add todo item...';
  input.onkeydown = (e) => {
    if (e.key === 'Enter') {
      attrs.add(input.value);
      input.value = '';
    }
  };
  return input;
}

class TodoList {
  ul =  as HTMLUListElement;
  add(v: string) {
    const item =
- {v}
 as HTMLLIElement;
    item.onclick = () => item.remove();
    this.ul.append(item);
  }
}

export default () => {
  const list = new TodoList();
  list.add('foo');
  list.add('bar');
  return
     list.add(v)} />
    {list.ul}
  ;
};

Processamento de grandes volumes de dados

  • A função FindNames processa e filtra grandes volumes de dados para exibir os resultados
  • Os itens correspondentes são atualizados em tempo real conforme o valor de entrada
import { data } from "../fetch-dataset.js";
export default function FindNames() {
  const status =  as HTMLParagraphElement;
  const results =  as HTMLUListElement;
  const input =  as HTMLInputElement;
  updateMatches();

  function updateMatches() {
    const matched = (data.entries().filter(([k]) => k.match(input.value)).toArray());
    const matches = (Iterator.from(matched).map(match => ).take(30));
    results.replaceChildren(...matches);
    status.textContent = `${matched.length} / ${data.size}`;
  }

  return
    {input}
    {status}
    {results}

;
}

function Item(attrs: { match: [string, number], regex: string }) {
  const [name, count] = attrs.match;
  const total = ({count});
  return
-
     {total}

;
}

function highlight(str: string, regex: string) {
  if (!regex) return str;
  const r = new RegExp(`(${regex})`, 'gi');
  return str.replace(r, '$1');
}

Introdução ao imlib

  • imlib é uma biblioteca desenvolvida para o immaculatalibrary.com
  • Ela é usada para construir o minigamemaker.com e o site que você está lendo agora
  • Foi desenvolvida porque o estado da arte existente não era suficiente, e é a forma preferida de criar apps

Resumo do GN⁺

  • Este artigo explica como criar e interagir diretamente com elementos DOM usando JSX
  • Ele apresenta uma forma de processar grandes volumes de dados com eficiência sem usar um virtual DOM
  • A biblioteca imlib permite desenvolver apps de maneira simples e intuitiva
  • Outros projetos com funcionalidades semelhantes incluem React e Vue.js

1 comentários

 
GN⁺ 2024-08-17
Opiniões no Hacker News
  • Retornar nós DOM reais praticamente neutraliza grandes vantagens do JSX e de bibliotecas não JSX como Lit: APIs em estilo modo imediato e o modelo UI=f(state)
    É preciso retornar uma descrição do DOM, não o DOM real, para reavaliar o template com o novo estado e atualizar com eficiência o DOM em que esse template é renderizado
    Os exemplos atualizam usando APIs imperativas do DOM, como input.onkeydown = ... ou this.ul.append(item), mas evitar esse tipo de código e percorrer os itens no template provavelmente é o principal benefício do DOM virtual
    Dito isso, o problema do DOM virtual é que o cálculo de diferenças é lento, e isso pode ser resolvido com templates que separam partes estáticas e dinâmicas, como no Lit

    • Pela minha experiência criando UIs web complexas, em apps suficientemente complexos, o benefício de desempenho do DOM virtual é quase uma ilusão
      Pelo contrário, ele leva a pensar “posso renderizar de novo uma UI grande à vontade que o DOM virtual dá conta”, o que acaba tendo efeito contrário: o desempenho real fica ruim, mas a estrutura do app já se cristalizou de um jeito difícil de corrigir sem reescrever
      Vi o mesmo padrão em quatro equipes e, como arquiteto, recomendei uma estrutura vanilla consciente de desempenho com base nos problemas que teríamos com React, mas isso não foi aceito sob o argumento de que “há muitas maneiras de evitar”
      No fim, parece que em muitos casos é preciso aprender sofrendo na prática
    • Isso depende do usuário. Por outro ponto de vista, JSX é apenas outra DSL para o padrão de chamadas à função createElement, e os benefícios descritos vêm do framework e do runtime
      Em termos mais curtos, JSX é uma sintaxe alternativa de chamada de função com usos úteis
      Em uma empresa anterior, usávamos JSX para fábricas de dados de teste; ele parecia XML, mas na etapa de criação dos elementos era possível tomar decisões contextuais ajustadas aos dados finais usando o padrão builder
      Não tinha relação com React ou DOM, nem significava que aquilo não pudesse ser expresso declarativamente sem JSX
    • O que “se quer” depende de quem está falando. Pessoalmente, prefiro atualizar nós DOM no lugar, sem nenhum mecanismo de reconciliação
    • Entendo o sonho de “não precisar mais fazer atualizações imperativas”. Usei frameworks desse tipo por uns 10 anos e estou bem acostumado a eles
      Pessoalmente, prefiro atualizar o DOM de forma imperativa. Dá para controlar com muita precisão o que acontece e projetar uma estrutura muito eficiente
      Também fica mais fácil adicionar, alterar, remover e corrigir funcionalidades do app sem precisar moldar o raciocínio à metodologia rígida de outra pessoa
    • Concordo que retornar nós DOM reais enfraquece as vantagens de APIs em estilo modo imediato e do modelo UI=f(state), que são pontos fortes do JSX e de bibliotecas não JSX como Lit, mas isso parece uma das abstrações com vazamento mais marcantes de toda a computação
      Há um motivo para ter surgido todo um ecossistema em torno do React sobre como evitar múltiplas renderizações e impedir atualizações desnecessárias
  • Criei este projeto por frustração com o estado dos geradores de sites estáticos nos últimos 10 anos
    Como faço principalmente sites estáticos, queria uma ferramenta simples e intuitiva, e JSX parecia se encaixar bem, mas eu estava cansado da complexidade excessiva de frameworks JSX como React
    Então criei um gerador de sites estáticos que renderiza JSX como strings, e estendê-lo para o lado do navegador, fazendo JSX renderizar como elementos DOM, foi um passo natural
    Em alguns casos, especialmente em layouts, ele também funciona bem para componentes compartilhados. No geral, estou satisfeito com o resultado, embora algumas partes sejam bem hacky e o suporte das IDEs ainda possa melhorar
    Além disso, essa abordagem também se encaixa bem com SEO. Era um problema que eu não sentia que tivesse sido resolvido adequadamente em outros frameworks JSX

    • Você chegou a ver o Astro? Ele parece um gerador de sites estáticos perfeito, embora talvez houvesse outras frustrações que o VanillaJSX resolvesse
    • Boa ideia. Na página de demonstração, o app de tarefas retorna 404
      Na documentação, talvez seja melhor escrever “static site generator” por extenso em vez de “SSG”. Eu já usei geradores de sites estáticos, mas não sabia o que era SSG e tive que perguntar a uma IA
    • Seria legal ter JSX para server-side no Django
    • Fiquei curioso se você chegou a olhar o lit-html
      Vim do Vue e me surpreendi: o lit-html faz muitas coisas que os templates do Vue fazem, incluindo ligar eventos, usando apenas templates em JS vanilla simples
      Com a extensão lit do VSCode, dá para ter destaque de sintaxe e verificação completa de tipos até dentro dos templates
      Descobri isso depois que Marc Grabanski tweetou que usa lit-html com vanillajs, e não Lit; ao experimentar por conta própria, funcionou muito bem. Parece bastante parecido com o que você está tentando resolver agora
      Usando o pacote de templates lit-html, dá para fazer a maior parte do que é descrito no capítulo de templates: https://lit.dev/docs/templates/overview/
      Também é possível sem a abstração de componentes do lit-element: https://lit.dev/docs/libraries/standalone-templates/#renderi...
  • O motivo pelo qual esses “e se” são interessantes é que a origem do JSX pode ser rastreada até o XHP do Facebook, e o XHP foi explicitamente inspirado no E4X, um padrão JS inicial que se parecia e se comportava de forma semelhante à biblioteca descrita aqui
    [1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...
    [2] https://en.m.wikipedia.org/wiki/ECMAScript_for_XML

    • O E4X tinha o infeliz inconveniente de retornar instâncias reais do DOM, então elas precisavam ser atualizadas de forma imperativa
      Esse também foi o motivo pelo qual o JSX foi além do E4X, e depois disso não houve nenhuma proposta séria para templates HTML dentro do JS
      Ainda assim, parece possível reviver a ideia geral com um acabamento moderno: https://github.com/WICG/webcomponents/issues/1069
    • Curiosidade: o E4X é o motivo de o JavaScript ter ganhado for(of) em vez de for each
      O motivo de não termos ganhado for (:) é ainda mais absurdo: era porque poderia conflitar com anotações :type que alguns membros do TC39 acreditavam que um dia entrariam magicamente na linguagem
  • Já criei uma biblioteca de UI com a ideia de que expressões de template JSX criassem nós reais do DOM
    Ela também eliminava parte do boilerplate de handlers de eventos imperativos ao vincular objetos de modelo a atributos. Como é de se imaginar, acho que é uma boa ideia
    https://github.com/tomtheisen/mutraction
    Dava para escrever código que cria um modelo com track({ clicks: 0 }), incrementa model.clicks ao clicar e reflete esse valor diretamente no DOM

  • Aqui também vale mencionar o VanJS: https://vanjs.org/
    Outras bibliotecas JSX, como Solid.JS, também retornam nós do DOM, e é bom ver essa ideia ganhando força
    Acho que quanto mais perto ficarmos da plataforma que usamos, melhor. Camadas de abstração podem ser úteis às vezes, mas na prática ainda não encontrei uma necessidade real de esconder a plataforma
    Projetos enormes como os do Facebook talvez se beneficiem disso, mas nunca trabalhei nessa escala

    • Na comparação de tamanho de bundle, SolidJS não é meio irrelevante?
  • Os exemplos são legais, mas parece importante que eles não mostrem props de componentes que possam mudar ao longo do tempo
    Essa capacidade em si não parece estar modelada. Se você não precisa disso, é uma abordagem inteligente, mas não consigo ver bem como isso escala para apps mais complexos

    • A técnica usada aqui e em todo o código do lado do navegador é exatamente a mesma usada dentro do VS Code, e escala muito bem
      A diferença é apenas escrever de forma mais concisa as 10 linhas de código que normalmente criariam e configurariam elementos do DOM
      Na verdade, a parte realmente interessante desse framework é quase todo o resto. Há o lado do gerador de sites estáticos retornando JSX como strings, a importação de diretórios de código-fonte bruto para manipular string|Buffer no momento do SSG, um sistema de módulos próprio, extremamente eficiente e rápido, construído sobre chokidar e swc, etc.
      Usar JSX como DOM é a parte visualmente mais chamativa, mas na prática é quase um truque de festa
      Por exemplo, o código-fonte do vanillajsx.com é muito conciso, claro e curto. Escrevi tudo hoje, sem dependências além de imlib, e a demo de JSX-as-DOM é a parte menos inovadora: https://github.com/sdegutis/vanillajsx.com/tree/main/site
    • Para levar essa abordagem a uma escala real e ter confiança de que tudo vai funcionar junto como esperado, é necessária uma camada de reatividade adequada
      Se você gosta dessa abordagem em que JSX retorna elementos concretos do DOM, o Solid funciona exatamente assim, com uma camada de reatividade adequada adicionada por cima
    • Posso estar deixando passar algo, mas não vejo como essa abordagem impede o uso de setTimeout ou setInterval
      Dito isso, concordo que esses projetos muitas vezes funcionam bem em casos de uso pequenos, mas desmoronam rapidamente em cenários do “mundo real”
    • Não estou defendendo essa biblioteca nem planejo usá-la, mas, se a ideia for fazer mudanças de props provocarem rerenderização como no React/Vue, aqui parece que daria para usar JS Proxy
      A implementação não parece ser tão difícil
  • Isto é muito parecido com Vanilla TSX: https://github.com/wisercoder/uibuilder
    Um exemplo de app escrito com Vanilla TSX está aqui: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...

  • Isso me lembra o ActionScript 3, que tinha XML no núcleo da linguagem
    Era uma linguagem divertida de escrever, mas falhou de forma célebre em se tornar o ES4. No fim, levou mais de 10 anos para chegarmos a um ponto parecido com TypeScript e JSX

    • https://en.wikipedia.org/wiki/ECMAScript_for_XML — também existia no Firefox, mas o público não queria, e acabou sendo removido
      Foi desativado para páginas web no Firefox 17, seis meses antes do primeiro lançamento do React
    • Não me lembro de ser possível criar XML inline desse jeito. Talvez fosse possível se isso fosse um recurso do lado servidor do Flex
  • Fiquei curioso se há mais alguém para quem o último exemplo não funciona no Firefox. Funcionou no Edge, mas falhou no Firefox
    Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function

  • Não entendo bem por que JSX é mais atraente do que escrever algo assim:
    h("div", {}, [ h("p", {}, "this is easy"), ...list.map((l) => h("li", {}, l)) ])
    Dessa forma, você ganha automaticamente loops, interpolação de variáveis etc. sem precisar criar um compilador e uma nova sintaxe. Gostaria que alguém explicasse

    • Acho que muita gente acharia a versão em JSX muito mais legível e familiar. Claro, é uma questão de gosto
      Para ser justo, no JSX você também usa loops comuns e interpolação do mesmo jeito. {} aceita expressões de JS puro
      Só é necessária uma etapa de compilação para expandir as tags de elementos em chamadas a createElement
    • Pessoalmente, quando vejo o código que você colocou, não entendo bem quem acha que ele é tão bom quanto JSX
      Cada pessoa processa informações de um jeito, mas aquele exemplo tem tanta pontuação que é muito difícil entendê-lo rapidamente
      A estrutura do DOM que será gerada não fica visível sem decompor mentalmente a sintaxe, e entender a estrutura do DOM de relance é muito mais importante do que precisar ou não de um compilador
      Para contexto, sou uma pessoa neurodivergente, e espero que essa explicação ajude
    • Se continuar descendo por essa toca de coelho lógica, você acaba chegando em Common Lisp
    • Já pensei a mesma coisa. Uma vantagem parece ser o fato de se parecer com HTML
      Como é parecido com o que se vê nas ferramentas de desenvolvedor do navegador, fica mais fácil comparar e depurar
    • Para um arquivo único ou um app simples, esse estilo também é ok
      Mas, em apps complexos, quando os componentes chegam às centenas e começam a importar uns aos outros, a forma de escrever com tags JSX passa a parecer uma sintaxe simples e fácil de entender
      A resistência ao React também vem muito do fato de ele ser exagerado para coisas simples — e de fato é
      Mas as pessoas criam apps com complexidades e requisitos diferentes, então não dá para simplificar dizendo que um lado é mais atraente. São ferramentas diferentes para problemas diferentes