- Para tratar as demos interativas de um tutorial sobre CRDTs como conteúdo duradouro, Web Components que podem ser inseridos diretamente no HTML são mais adequados do que um framework específico
- Os componentes MDX do Astro oferecem conveniências como build, escopo de CSS e TypeScript, mas podem gerar custo de reescrita ao serem movidos para fora do Astro
- Web Components permitem inserir tags customizadas como
<pixelart-demo></pixelart-demo> no Markdown e possibilitam reutilização baseada em tags em qualquer lugar da página apenas carregando um script
- Frameworks e TypeScript também são dependências; mudanças de versão, API e compatibilidade se acumulam, e quanto mais dependências houver em código abandonado por muito tempo, mais difícil será recuperá-lo
- A plataforma Web é estável o bastante para sites antigos funcionarem também em navegadores modernos; para trabalhos que precisam ser acessíveis daqui a 5, 10 ou 20 anos, uma abordagem baseada em padrões é vantajosa
Preservando demos interativas como conteúdo
- A série de posts sobre CRDTs precisava de demos interativas para demonstrar conceitos, usando como exemplo um editor colaborativo de pixel art
- O critério de escolha da tecnologia era que, mesmo que a demo fosse feita com HTML, CSS e JS, ela se aproximava mais de conteúdo incorporado ao post do blog
- Algo mais parecido com um elemento inserido no texto, como uma imagem ou um vídeo
- Deveria poder ser movida para qualquer lugar capaz de renderizar HTML
- O site era feito com Astro em 2023, mas antes já havia passado por um gerador próprio de site estático, Hugo, um CMS customizado em PHP, Tumblr, Movable Type, WordPress etc.
- Mesmo gostando do Astro hoje, é difícil presumir que o site vá rodar sobre Astro para sempre
Como o Markdown reduziu o custo de migração
- O motivo de as migrações recentes terem sido fáceis foi manter o conteúdo como arquivos de texto simples em Markdown
- Isso reduz tarefas normalmente necessárias ao trocar de sistema
- Exportar de um sistema e importar para outro
- Corrigir problemas de compatibilidade
- Remover elementos para os quais não se encontrou uma forma de migração
- Ao colocar arquivos Markdown em um novo site, a maior parte funciona como está
- Como o Markdown permite escrever HTML diretamente, diagramas interativos complexos também podem ser portados como o restante do conteúdo se puderem ser representados como tags HTML comuns
Conveniências e limites de portabilidade do MDX do Astro
- A integração MDX do Astro permite renderizar componentes Astro dentro do Markdown
- Componentes Astro aproveitam várias conveniências do sistema de build
- Escrever HTML, CSS e JS em um único arquivo
- Aplicar escopo a seletores CSS
- Compilar TypeScript
- Renderizar marcação condicional
- Outras otimizações
- O problema é que todas essas conveniências funcionam de forma confiável apenas dentro do Astro
- Ao migrar para outro gerador de sites, pode ser necessário reescrever os componentes
- Pode ser necessário separar HTML, CSS e JS
- Pode ser necessário configurar um novo sistema de build
- Pode ser necessário encontrar uma nova forma de aplicar escopo aos estilos
- Por isso, embora recursos específicos do Astro sejam convenientes, eles se tornam uma escolha inadequada para demos que precisam de preservação de longo prazo
O que Web Components resolveram
- Web Components são um conjunto de tecnologias padrão da Web para criar elementos HTML reutilizáveis
- O modo de uso consiste em escrever uma classe de elemento customizado, registrar um nome de tag e então usar essa tag na marcação
- A demo de pixel art é incorporada ao Markdown inserindo apenas o HTML abaixo
<pixelart-demo></pixelart-demo>
- Não é necessário nenhuma configuração especial separada nem o procedimento de posicionar primeiro um elemento específico e depois chamar uma função
- É preciso manter o arquivo JS e vinculá-lo com uma tag
<script>, mas isso é semelhante a outras mídias no sentido de que conteúdo textual precisa referenciar a mídia
- Quando o script é carregado, o nome da tag é registrado; mesmo que a marcação já exista antes da execução do JavaScript, ela funciona em qualquer lugar da página
Estrutura em arquivo único e Shadow DOM
- Web Components podem encapsular HTML, CSS e JS em um único arquivo, sem exigir um sistema de build separado
- Quando o código do componente fica reunido em um só lugar, a carga mental diminui; componentes de arquivo único também trazem vantagens para a experiência de desenvolvimento
- O componente de exemplo
<pixelart-demo> herda de HTMLElement, anexa um Shadow DOM com attachShadow({ mode: "closed" }) e então renderiza a marcação e os estilos internos
- O
import PixelEditor from "./PixelEditor.js"; no topo do arquivo é um import de módulo ES, portanto não depende de um sistema de build separado
- Se os arquivos relacionados forem mantidos juntos, o navegador processa os módulos
- O Shadow DOM isola o componente da página ao redor
- Pode ser estranho quando é preciso compartilhar estilos com o app inteiro
- É adequado quando o componente deve ficar completamente isolado
- Permite manter a mesma aparência e o mesmo comportamento onde quer que seja usado, como uma imagem ou um vídeo
Recebendo configuração externa por atributos
- Web Components podem expor atributos (attributes) configuráveis externamente
- Atributos podem ser usados como atributos nativos
- Variáveis CSS estão entre os poucos valores que podem entrar no Shadow DOM, então o exemplo de range slider define a cor de accent assim
<range-slider style="--accent: #0085F2"></range-slider>
- A demo de pixel art muda a resolução para 20 e exibe informações de depuração de cada pixel por meio de atributos
<pixelart-demo debug resolution="20"></pixelart-demo>
- Chamadas a
getAttribute e hasAttribute na classe do componente são usadas para ler esses valores de atributos
- Isso foi útil para reutilizar o mesmo componente em várias etapas do tutorial, ativando determinados recursos conforme a etapa
Frameworks e TypeScript também são dependências
- Existem ferramentas que compilam para Web Components, como Lit, Stencil e Svelte, mas frameworks são, no fim, dependências
- O trade-off que mais preocupava aqui era o custo de manutenção de longo prazo
- TypeScript também não é uma base nativa da Web, portanto conta como dependência
- As 15 versões recentes do TypeScript tiveram breaking changes e, embora muitas dessas mudanças fossem novos recursos úteis, elas exigiram alterações no código
- Ao usar dependências, vêm junto os custos de novos lançamentos de versão, mudanças de API e verificação de compatibilidade
- Como não há planos de mexer continuamente nesse código, a situação é não querer processar de uma vez vários anos de atualizações quando for realmente necessário corrigi-lo no futuro
Experiência recuperando sites antigos e a estabilidade da plataforma Web
- Ao criar um museu online, foi recuperado código antigo armazenado em um notebook que não era ligado havia 10 anos, e quanto mais dependências um site tinha, mais difícil era trazê-lo de volta à vida
- Em cerca de 20 anos criando para a Web, foi possível ver o surgimento, crescimento e declínio do jQuery, além da criação do Node.js e do fork e reunificação com io.js
- Backbone foi rapidamente substituído por AngularJS, AngularJS por React, e o próprio React passou por várias formas de escrever componentes ao longo de mais ou menos metade desse período
- Enquanto o ecossistema mudava, a própria plataforma Web foi alterada com cuidado para não quebrar sites existentes e permaneceu bastante estável
- Sites antigos ainda funcionam em navegadores modernos
- Se você quer um trabalho acessível daqui a 5, 10 ou 20 anos, é melhor reduzir dependências que não é possível controlar e se apoiar em padrões Web com maior probabilidade de não quebrar
- Mesmo com seus defeitos, quando se cria levando em conta suas características e sem camadas intermediárias, a Web se torna uma plataforma de computação resiliente, portátil e voltada para o futuro
1 comentários
Opiniões do Hacker News
htmx não é um framework, é uma biblioteca, e, como foi mencionado no texto, quero responder a alguns pontos
Primeiro, o texto não trata do problema de sincronizar o estado com o servidor, enquanto o htmx se concentra exatamente nisso por meio de trocas de hipermídia. Portanto, sob esse ponto de vista, htmx e Web Components são ortogonais entre si
Como dependências trazem novas versões, mudanças de API e custos para manter compatibilidade, o htmx não tem dependências e é muito focado em compatibilidade retroativa. Por exemplo, ele é compatível com IE11, e o intercooler.js, que é como o htmx 1.0, saiu em 2013 com uma única dependência, jQuery, mas ainda é suportado
Muitas bibliotecas JavaScript têm uma cultura de reescritas de API e upgrades difíceis, mas o htmx explicitamente não é assim, e não deveria ser enquadrado nessa generalização. A única mudança incompatível do htmx 2.0 provavelmente será algo como encerrar o suporte ao IE
Se Web Components vão durar mais que htmx, por fazerem parte do navegador, provavelmente sim, mas o htmx continuará, tanto quanto possível, muito estável e com poucas mudanças. A API está basicamente na direção certa, e não acho que seja preciso reescrever tudo em nome de pureza ou estilo
Acima de tudo, o htmx responde a uma pergunta diferente de Web Components: como sincronizar estado com o servidor
Ao usar WebComponent, é possível se conectar ao ciclo de vida de elementos DOM modificados externamente, como no morphdom do Htmx. Quando um atributo muda, é preciso avisar o componente React; quando um nó é removido do DOM, é preciso avisar o React para que execute o procedimento de limpeza. Sem WebComponents, acho que teria sido difícil fazer isso de forma limpa e eficiente
Como resultado, podemos continuar no modelo do Htmx, deixando todo o estado da lógica de negócio no servidor e mantendo no cliente apenas algumas coisas conservadoras, como o elemento atualmente em foco ou o texto que está sendo digitado. Quanto mais seguimos nessa direção, menos bugs e condições de corrida aparecem, e a experiência do usuário fica muito melhor, então vale o esforço
https://x.com/jlazaroff/status/1717176726248108469
https://htmx.org/essays/hypermedia-friendly-scripting/
A limitação de que apenas formulários podem fazer POST e de que a única forma de lidar com a resposta é reescrever a página inteira parece arbitrariamente restritiva. Se o htmx virasse algo como HTML6, ele duraria mais que todos os frameworks JavaScript
Como sou um engenheiro focado principalmente em backend, a maioria das bibliotecas JS que acabo usando depois traz um grande ônus de manutenção e mudanças frequentes em APIs principais. Estou pensando em testar o htmx em um projeto pessoal
Acho que os templates que geram fragmentos de HTML no lado do servidor também ficariam menos complexos
O htmx em si é um conceito realmente convincente, mas eu gostaria que houvesse uma comunidade maior. É um problema de ovo e galinha, mas, em comparação com outros frameworks web, não há tanto material como posts de blog ou vídeos. Por exemplo, é difícil encontrar até conteúdos básicos sobre como integrar autenticação do Auth0 a uma aplicação htmx
Escrevi o non.io inteiro em Web Components puros, e ele é uma SPA completa. Ao ir além de simples componentes de blog aninhados, aprendi algumas coisas
Gerenciamento de estado ainda não tem uma resposta clara. Se você não usa um framework wrapper, precisa fazer muita coisa por conta própria. Uma das coisas boas do React é que o gerenciamento de estado está em grande parte resolvido, com padrões e expectativas já estabelecidos. Em Web Components, parece um faroeste, e como não dá para passar objetos complexos como atributos do jeito que se faz com JSX, você acaba misturando atributos, eventos e chamadas diretas a funções de classes
O desempenho também não trouxe um ganho tão grande quanto eu esperava. Para pequenos elementos atômicos, como ícones, quando há centenas deles na página, o custo de criação das instâncias supera o valor, então voltei atrás e tirei de Web Components. Mesmo que elementos estáticos melhorem a legibilidade do código, não valia a pena usá-los como Web Components, o que foi bem decepcionante
Ainda assim, há coisas de que gosto muito. Como o autor disse, é muito fácil adicionar componentes a uma nova página, independentemente do framework usado. Se foi feito como Web Components, dá mesmo para usar em qualquer lugar. Ao criar sites auxiliares, como a documentação da API do nonio, foi ótimo poder escolher e usar os componentes desejados mesmo com uma stack diferente. Se você precisa embutir um componente, basta importar o JS e adicionar o HTML
O encapsulamento de estilos do shadow root também é muito bom. Gosto de não precisar me preocupar se vou quebrar o estilo de algum componente enquanto trabalho na página. Diminui o trabalho de adivinhar e verificar se vou sobrescrever o estilo de outros elementos, e também reduz a necessidade de frameworks de verificação de diferenças visuais para garantir isso
Também gosto do tamanho pequeno do pacote. Não há bibliotecas nem carregamentos extras; tudo é nativo. Como é fácil selecionar apenas os componentes necessários, também fica muito simples manter o tamanho do pacote baixo
Os componentes-fonte do nonio estão aqui: https://github.com/jjcm/nonio-frontend/tree/master/component...
Também não há absolutamente nada no gerenciamento de estado que impeça você de conectar qualquer solução de estado que quiser. Se o objetivo é minimizar dependências, já vi várias soluções usando o modelo de eventos nativo do navegador
Por fim, pessoalmente acho uma ideia bastante forçada escrever um app inteiro só com Web Components puros. Com 5KB, usar Lit dá uma experiência de desenvolvimento muito melhor: https://lit.dev
Esses frameworks tendem a ganhar desempenho fazendo o processamento em lote de manipulações do DOM na página inteira, e isso não é possível com Web Components
Só por esse motivo, vejo Web Components quase como um beco sem saída. Podem ser úteis quando você precisa apenas de pequenas ilhas de funcionalidade, em vez de uma página totalmente renderizada no cliente
Eu também vi limites de desempenho parecidos, mas sempre achei que fosse porque o shadow DOM era pesado. Agora fico pensando se a própria criação de instâncias é que é pesada
Senti o mesmo problema com gerenciamento de estado e estou pensando em pegar uma ideia emprestada do desenvolvimento desktop: colocar um barramento de mensagens no nível da janela, com todos os componentes assinando e filtrando mensagens, e publicar todas as mudanças de estado nesse barramento
Para um exemplo de passagem de objetos complexos como propriedades, veja o zx-listeditor aqui:
https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
O próprio Web Component está aqui:
https://github.com/wisercoder/uibuilder/blob/master/WebCompo...
A pequena biblioteca de 500 linhas que possibilita isso está aqui:
https://github.com/wisercoder/uibuilder/#jsx-for-web-compone...
O padrão básico do LiveState é “disparar eventos e assinar o estado”. Eventos e atualizações de estado são enviados por uma conexão WebSocket, e as bibliotecas de frontend e backend são uma camada fina sobre Phoenix Channels
Atualmente, as funções de manipuladores de eventos são escritas em Elixir, mas há trabalho em andamento para permitir escrevê-las em qualquer linguagem que compile para WebAssembly
Não espero que o framework JS preferido continue sendo o mesmo. Sei que eles têm vida curta, e gosto disso
Avançar, progredir e melhorar é o motivo de usar React, Vue, Svelte etc. Não quero algo estático agora. Isso porque nenhum dos frameworks disponíveis hoje, incluindo Web Components, resolve os problemas de modo particularmente elegante
Se a afirmação é que sites feitos com Web Components duram mais que sites feitos com frameworks JS, eu toparia uma aposta. Sinceramente, acho mais provável que, nos próximos 20 anos, alguma parte da stack de Web Components seja removida do Chrome, Safari ou Firefox e quebre apps do que um app em React 16 escrito há 2 anos quebrar por mudanças no JavaScript
Se você fixar completamente todas as versões das dependências de um app React 16, talvez ele não quebre, mas isso está mais perto de rodar um programa antigo numa VM ou emulador do que de “poder continuar trabalhando e hackeando em um app React 16”
Uma aposta mais significativa seria “é possível continuar desenvolvendo em um app React ZZZ vs. é possível continuar desenvolvendo em um app feito com Web Components”
Se eu fosse formular os termos de um jeito favorável ao meu ponto de vista, mas ainda mais significativo, seria: “o custo de atualizar um app React para acompanhar N anos de atualizações de dependências é maior ou menor do que o de um app com Web Components”. Se você não encheu o app de Web Components com dependências estranhas, acho muito provável que o custo de atualização seja maior no lado do app React
Basicamente, é apostar que os fornecedores de navegadores vão quebrar menos a compatibilidade retroativa do que os autores do React
HTML Imports foi descontinuado em favor de uma solução só em JS, e o Firefox já tinha implementado
Custom Elements v0 também foi descontinuado; o Chrome o implementou, e o YouTube foi reescrito às pressas com isso. Como o YouTube levou esse tempo para migrar, foi preciso esperar cerca de 4 anos até remover a implementação
Para manter o código seguro, é preciso atualizar as bibliotecas. Mas versões antigas de bibliotecas deixam de ter suporte, e vulnerabilidades conhecidas que não podem ser corrigidas começam a se acumular. É muito provável que atacantes tenham programas que escaneiam sites com vulnerabilidades conhecidas e realizam ataques automaticamente
Por outro lado, se você usa uma biblioteca ou plataforma que valoriza a compatibilidade retroativa, como Web Components, é possível manter a segurança e, ao mesmo tempo, dar suporte a código antigo por mais tempo
Acabo focando em pessoas mais do que em tecnologia
Se eu fosse iniciar um novo projeto, primeiro olharia o que as pessoas que vão trabalhar nele conhecem. Se houver um denominador comum ou uma boa prática, como React é hoje no frontend, simplesmente uso isso
Não fico quebrando muito a cabeça com esse tipo de decisão técnica. A composição da equipe, o mercado de candidatos e os rankings de tecnologias do StackOverflow respondem facilmente a esse tipo de pergunta
Mas, se todo mundo pensar assim, mesmo que exista uma resposta melhor para criar software, vamos usar React para sempre. Dizer que vai usar o que é popular significa depender de outras pessoas para encontrar boas soluções técnicas e torná-las populares, a fim de melhorar sua stack tecnológica
Em outras palavras, essa estratégia certamente fica mais para o lado dos adotantes intermediários a tardios na curva de adoção. Boa parte destes textos e discussões é voltada a adotantes mais iniciais
Como é possível usar HTML dentro de Markdown, a ideia de que até diagramas interativos elaborados podem ser portáteis como o restante do Markdown, desde que possam ser expressos com tags HTML comuns, é algo que MDX também consegue fazer exatamente da mesma forma
Aqui, a tecnologia Web Components em si não ajuda. O que importa para o autor é a interface entre a marcação e o trecho de código que aplica a funcionalidade em tempo de execução. Web Components, aqui, são apenas um detalhe de implementação. Não importa se, ao escrever um nome entre sinais de menor e maior, você está referenciando um componente React, algo do Astro ou um Web Component. O ponto central é que alguma outra coisa é invocada
A desvantagem dos Web Components aqui é que, na prática, eles rodam apenas no cliente. Como não há uma forma de passar para o cliente o conteúdo de shadow DOM renderizado no servidor, mesmo que o Web Component seja totalmente não interativo, ele precisa de JS no cliente para ser renderizado. Outros frameworks não são assim
Isso não quer dizer que o ponto do autor esteja errado. Como forma de sistematizar a interface entre trechos de código, Web Components talvez sejam a melhor e mais duradoura abordagem. Mas, no servidor, Web Components não ajudam em nada. Eles não são renderizados, a interface é trabalhosa, e é muito provável que o código já esteja escrito de uma forma que possa ser tratado como um todo. Se você está usando Astro no servidor, é só continuar usando Astro. O mesmo vale para React, Vue ou qualquer outro
Onde Web Components brilham é no cliente. Ao usar componentes de terceiros que podem não ter sido escritos no framework escolhido, você não precisa saber com qual framework o componente colocado na página foi feito. Use a tag HTML e ele funciona. O mecanismo de interface sobe para o nível do navegador, então dois frameworks não precisam conhecer os detalhes de implementação um do outro
Nesse sentido, é verdade que alguma ferramenta é necessária. Mas essa ferramenta pode ser qualquer mecanismo de template do lado do servidor que preencha o HTML inicial e faça o bootstrap
Mas shadow DOM renderizado no servidor pode, sim, ser passado para o cliente. Isso se chama shadow DOM declarativo e funciona em todos os principais navegadores, exceto no Firefox. O Firefox também já está trabalhando nisso, e há um polyfill
https://developer.chrome.com/articles/declarative-shadow-dom...
A propósito, esse polyfill é bem simples, então talvez dê para usá-lo mesmo que o navegador ainda não ofereça suporte
Se surgirem Web Components renderizáveis no servidor, isso abrirá muitas possibilidades para melhorar a experiência do usuário em apps multipágina
O mundo JS ainda é obcecado demais por clientes pesados que controlam tudo, e acaba deixando passar coisas interessantes que estão acontecendo no mundo do servidor
Soluções como Rails Hotwire, Phoenix LiveView e Laravel Livewire conseguem resolver 80% do frontend com JS mínimo e quase nenhuma complexidade de experiência de desenvolvimento
Já criei apps frontend ricos em recursos que leem e escrevem em banco de dados e armazenamento de objetos sem servidor. A portabilidade é muito boa, e a instalação se resume a fazer upload de alguns arquivos. Eles rodam direto a partir de um CDN simples e são muito rápidos
A maioria dos desenvolvedores frontend que conheço também conhece a família Hotwire, mas, para eles, esse tipo de stack aumenta a complexidade da experiência de desenvolvimento. Às vezes as pessoas dizem “complexidade”, mas na verdade querem dizer “falta de familiaridade”. Frontend e backend podem ambos ficar complexos demais, mas não precisam ser assim
Quando as coisas dão errado, acho melhor ter mais controle. Especialmente em ambientes rápidos, em que o cliente exige o que quer imediatamente
Pessoalmente, prefiro escrever tudo em JavaScript/TypeScript a espalhar açúcar sintático sobre HTML
Colocar isso do lado do servidor, como no Livewire, não elimina desvantagens fortes como latência
O título provavelmente está certo, mas não sei se isso importa. A maioria das aplicações não vive tanto tempo assim
Usei Web Components recentemente, e uma funcionalidade muito simples em qualquer framework foi realmente confusa e complicada de configurar. Se o componente for simples, talvez não faça diferença, mas, se você está criando uma aplicação em JS, slots são um recurso essencial porque permitem criar componentes combináveis
Depurar shadow DOM nas ferramentas de desenvolvedor também foi realmente confuso
renderProp do React ou scoped slot do Vue são muito usados para injetar dados de um componente pai em um componente filho. Mas reproduzir esse comportamento em um Web Component é praticamente impossível sem hacks com MutationObserver. A suposição dos Web Components de que o componente filho e o componente pai não precisam saber informações um do outro não condiz com o mundo real
A suposição de que o componente filho sempre é carregado e inicializado independentemente do pai também é falsa na maioria dos frameworks. Na maioria dos frameworks, o componente pai decide se vai de fato carregar ou inicializar o filho
Web Components podem ser melhores para criar componentes isolados e reutilizáveis, mas a DOM API usada para juntar uma coisa à outra e fazer a aplicação funcionar é simplesmente péssima
Fica evidente que ela foi criada para editar documentos XML
Há mais um motivo para eu não criar Web Components nem mesmo para componentes isolados e reutilizáveis: nenhum editor consegue lidar direito com coisas como autocompletar nomes de atributos ou eventos, ou verificar erros de digitação
Para torná-los úteis, é preciso conectar várias dependências, e essas dependências provavelmente não serão tão bem mantidas quanto o React
Já trabalhei em uma empresa que seguia essa filosofia. Todos os componentes de UI eram Web Components com seus próprios pacotes npm que precisavam ser instalados. Funcionava bem, mas não havia benefício nenhum. Seis anos depois, ainda não há benefício. No fundo, é otimizar para um futuro que talvez nunca chegue
Sites em HTML/CSS vão durar mais do que todo o resto
Porque texto, imagens e layout ficam dentro de algo que é realmente estático, pode viver para sempre sem ser mexido e não exige executar código complexo nem manter semipadrões ambíguos que mudam rapidamente
Se você quer criar sites duradouros e ter tecnologia duradoura, basta aprender a criar sites HTML com aprimoramento progressivo. Criar elementos HTML indefinidos e inúteis com hífen, como em Web Components, e depois executar um monte de JavaScript para defini-los, ou usar frameworks JavaScript que nem fingem usar HTML, vai durar só alguns anos
Pelo menos até os navegadores removerem o suporte a HTTP/1.1