1 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Mantém a renderização no servidor baseada em html/template do Go enquanto adiciona interações dinâmicas com HTMX, retornando seletivamente páginas completas e fragmentos HTML a partir de um único renderizador
  • Divide a marcação em base.tmpl, templates de página e fragmentos reutilizáveis, e simplifica a implantação e a estrutura de renderização com embed.FS do Go 1.16 e um conjunto compartilhado de templates
  • Dependendo de HX-Request, retorna resultados de busca como página completa ou como linhas de tabela, evitando erros de cache e de restauração ao voltar com Vary: HX-Request e historyRestoreAsHxRequest: false
  • Ao redirecionar requisições HTMX para outra página, usa HX-Redirect e respostas 2xx em vez de 3xx comuns, e controla o escopo de substituição e a exibição de erros com responseHandling para 204, 422, 4xx e 5xx
  • Usa como ponto de partida uma configuração que desativa cache em armazenamento local e herança de atributos, além de definir tempo limite das requisições; conforme a escala aumenta, pode adicionar templates de layout intermediários para suportar estruturas de tela separadas, como uma área administrativa

Por que combinar a renderização no servidor do Go com HTMX

  • O HTMX permite adicionar interações dinâmicas com pouco JavaScript, mantendo a consistência e a segurança da renderização de HTML no servidor oferecida por html/template do Go
  • A implementação trata de três frentes
    • estrutura de templates que dá suporte tanto a páginas completas quanto a respostas HTML parciais
    • redirecionamento e tratamento de erros no ambiente HTMX
    • configuração que altera os valores padrão do HTMX e o motivo disso
  • Começa com uma funcionalidade básica de trocar um botão por uma imagem e evolui para uma tela de busca que filtra a lista enquanto o usuário digita nome de usuário ou e-mail
  • O mesmo padrão de template pode ser aplicado não só ao HTMX, mas também a ferramentas HTML-over-the-wire como Unpoly e Hotwire
  • Se você ainda não conhece o uso básico do HTMX, vale a pena consultar primeiro a documentação oficial

Estrutura de diretórios do projeto e arquivos estáticos

  • O projeto é dividido nas seguintes áreas conforme a função
    • assets/html/base.tmpl: estrutura HTML compartilhada por todas as páginas
    • assets/html/pages/: conteúdo de páginas individuais
    • assets/html/partials/: fragmentos HTML reutilizados em vários pontos
    • assets/static/css, assets/static/img, assets/static/js: ativos estáticos
    • cmd/web/: inicialização do servidor, handlers e código do renderizador HTML
  • A estrutura básica é criada com os comandos a seguir
go mod init example.com/htmx
mkdir -p assets/static/css assets/static/img assets/static/js assets/html/partials assets/html/pages cmd/web
touch assets/efs.go assets/html/base.tmpl assets/html/partials/images.tmpl assets/html/pages/home.tmpl cmd/web/main.go cmd/web/handlers.go cmd/web/html.go
  • O HTMX pode ser instalado por CDN ou NPM, mas aqui se usa principalmente a abordagem de baixar uma cópia e servi-la diretamente como arquivo estático da aplicação
    • a configuração fica mais simples e evita as desvantagens do uso de CDN
    • são usados htmx.org@2.0.10 e o framework CSS sem classes bamboo.css@1.4.0
wget -P assets/static/js https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js
wget -P assets/static/css https://cdn.jsdelivr.net/npm/bamboo.css@1.4.0/dist/bamboo.min.css

Separando templates base, de página e parciais

  • base.tmpl define a estrutura completa do documento, como <head>, título comum e <main>, e insere page:title e page:content
  • O Bamboo CSS e o script do HTMX são carregados no template base, e o script do HTMX recebe o atributo defer
    • o navegador busca o script em paralelo enquanto faz o parsing do HTML
    • o script é executado depois que o parsing do HTML e a construção do DOM terminam
  • Todos os templates recebem nomes explícitos com {{define}}...{{end}}, sem depender do nome do arquivo
    • o código Go pode referenciar os templates de forma consistente usando apenas os nomes definidos
    • os : em page:title são apenas um separador arbitrário, e nomes como page_title, page-title, pageTitle ou title também seriam possíveis
  • O botão da página inicial define dois comportamentos
    • hx-get="/gopher": envia uma requisição GET /gopher ao clicar
    • hx-swap="outerHTML": substitui o próprio botão pelo HTML da resposta
  • partial:image:gopher é um fragmento de imagem reutilizável, e recebe a largura da imagem no momento da chamada por meio de width="{{.}}"
  • Fragmentos HTML usados apenas em uma página específica podem ficar no próprio arquivo da página, em vez do diretório compartilhado partials
    • a ideia é separar fragmentos usados em várias páginas no diretório comum e colocar os fragmentos exclusivos de uma página ao lado do conteúdo relacionado

Incluindo HTML e ativos estáticos no binário Go

  • Desde que o Go 1.16 adicionou incorporação de arquivos, é possível incluir HTML e ativos estáticos no binário Go em vez de lê-los do disco em tempo de execução
  • A diretiva //go:embed "html" "static" coloca os dois diretórios em um embed.FS, e fs.Sub() os separa em dois subsistemas de arquivos
    • HTMLFiles: sistema de arquivos de templates com html como raiz
    • StaticFiles: sistema de arquivos de arquivos estáticos com static como raiz
  • Há duas vantagens nessa separação
    • o código de HTML e o código de arquivos estáticos não acessam desnecessariamente os arquivos um do outro
    • não é preciso adicionar os prefixos html/ ou static/ ao abrir arquivos
  • Se você não quiser usar panic() em sub(), também pode retornar um erro e inicializar as duas variáveis em main()
  • Na implementação atual, para fs.Sub() falhar, o valor do diretório teria de ser um caminho inválido; como as strings fixas "html" e "static" passam nessa verificação, o risco de pânico em tempo de execução é muito baixo

Conjunto compartilhado de templates e htmlRenderer

  • htmlRenderer gerencia o fluxo de renderização da seguinte forma
    • ao iniciar, faz o parse de um conjunto compartilhado de templates uma única vez
    • a cada renderização, clona o conjunto compartilhado
    • faz o parse adicional dos templates de página necessários para a requisição
    • executa o template com o nome especificado e o envia como resposta HTTP
  • newHTMLRenderer() registra um template.FuncMap e então faz o parse dos templates comuns
    • conecta now a time.Now
    • outras funções personalizadas de template também podem ser adicionadas no mesmo ponto
  • render() cria um conjunto de templates por requisição com sharedTemplates.Clone() e, se houver additionalTemplateFiles, o expande com ParseFS()
  • Os templates são executados primeiro em um bytes.Buffer, e só depois o código de status é gravado e o corpo da resposta é enviado
  • Na inicialização do servidor, "base.tmpl" e "partials/*.tmpl" são parseados no conjunto compartilhado
    • o template base e todos os fragmentos comuns ficam sempre disponíveis no renderizador
    • apenas os templates específicos de página são adicionados na chamada do handler
  • Os arquivos estáticos são servidos com http.FileServerFS(assets.StaticFiles) na rota /static/, e o servidor roda em :5051

Renderizando páginas completas e fragmentos HTML

  • O handler da página inicial retorna o documento completo com a chamada abaixo
app.html.render(w, 200, nil, "base", "pages/home.tmpl")
  • Essa chamada adiciona pages/home.tmpl ao conjunto compartilhado e executa o template base com 200 OK
  • O handler de /gopher não faz parse de arquivos adicionais; ele executa apenas partial:image:gopher, que já está incluído no conjunto compartilhado
width := 100
app.html.render(w, http.StatusOK, width, "partial:image:gopher")
  • O HTMX substitui o botão pela imagem recebida de /gopher, de acordo com a configuração hx-swap="outerHTML"
  • Essa estrutura oferece as seguintes vantagens
    • Templates e assets estáticos ficam embutidos no binário, facilitando o deploy
    • O mesmo render() pode retornar tanto a página HTML completa quanto fragmentos específicos de HTML
    • Templates base e parciais reduzem a duplicação de markup
    • Templates parciais podem ser reutilizados no template base, no conteúdo da página e em outros templates parciais

Busca de usuários com atualização durante a digitação

  • A funcionalidade de busca é dividida em duas rotas
    • GET /users: página HTML completa com as informações de todos os usuários
    • GET /users/search: fragmento HTML que retorna apenas as linhas da tabela dos usuários cujo nome ou e-mail correspondem ao termo buscado
  • O campo de busca combina os seguintes atributos do HTMX
    • hx-get="/users/search" envia a requisição de busca
    • hx-trigger="input changed delay:500ms, keyup[key=='Enter']" faz a requisição após 500 ms de uma mudança na entrada ou ao pressionar Enter
    • O termo da busca é enviado como query string no formato GET /users/search?query=foo
    • hx-target="#search-results" insere a resposta dentro de <tbody id="search-results">
    • hx-push-url="true" atualiza a barra de endereço a cada requisição e adiciona um item ao histórico do navegador
  • users:rows é um template separado que renderiza apenas as linhas de usuário
    • Cada linha exibe nome e e-mail
    • Se IsGopher for verdadeiro, renderiza partial:image:gopher com largura 24
  • listUsers e searchUsers adicionam pages/users.tmpl ao conjunto compartilhado, mas executam templates diferentes
    • listUsers: executa base e retorna a página completa
    • searchUsers: executa users:rows e retorna apenas as linhas correspondentes
  • Se o termo de busca estiver vazio, usa-se a lista completa de usuários; se houver valor, strings.Contains() verifica nome e e-mail
  • Em uma aplicação real, vale considerar os seguintes aprimoramentos
    • Unificar os handlers de listagem e busca em um só
    • Centralizar helpers de tratamento de erro e logging
    • Adicionar, via middleware, cabeçalhos de Content Security Policy e recuperação de pânico
    • Configurar timeouts adequados no servidor

Distinguir página completa e resposta parcial com HX-Request

  • Se o usuário abrir /users/search?query=leo diretamente no navegador ou acessar por um link compartilhado, ele poderá ver apenas o HTML parcial correspondente às linhas da tabela
  • Requisições do HTMX sempre incluem o cabeçalho HX-Request: true, então ele é usado como critério para decidir o formato da resposta
func isHTMXRequest(r *http.Request) bool {
    return r.Header.Get("HX-Request") == "true"
}
  • O template padrão executado pelo handler de busca passa a ser base, e só muda para users:rows em requisições HTMX
    • Em acesso direto, o usuário recebe a página completa com os resultados da busca
    • Em requisições HTMX, retorna apenas o fragmento de linhas para inserir no <tbody>

Cabeçalho Vary e restauração ao voltar

  • Como a mesma URL retorna respostas diferentes conforme o valor de HX-Request, o cache também precisa ser informado dessa diferença
  • Adiciona-se Vary: HX-Request a todas as respostas renderizadas
    • Definir isso apenas nos handlers que precisarem seria, em tese, menos desperdiçador
    • Configurar tudo de forma centralizada no renderer evita bugs causados por omissões
  • Quando hx-push-url ou hx-boost adicionam entradas ao histórico do navegador, o HTMX armazena em cache o HTML completo da página no armazenamento local do navegador
    • O cache padrão é de até 10 páginas
    • Ao voltar para um ponto que não está no cache, o HTMX faz nova requisição ao servidor para aquela URL
  • Por padrão, a requisição de restauração inclui HX-Request: true, então o servidor pode retornar HTML parcial em vez da página completa
  • Se você escolhe o tipo de resposta com base em HX-Request, defina historyRestoreAsHxRequest: false
    • Isso remove HX-Request: true das requisições de restauração com cache miss
    • Assim, o servidor retorna a página HTML completa para essa requisição
  • A configuração do HTMX vai na meta tag htmx-config de base.tmpl

Redirecionar requisições HTMX para outra página

  • O HTMX consegue substituir imediatamente fragmentos HTML, como mensagens de sucesso, o que reduz a necessidade do fluxo tradicional Post/Redirect/Get
  • Quando for necessário enviar o usuário para uma página completamente diferente, como ir para o perfil após login bem-sucedido, não dá para resolver isso apenas com uma resposta 3xx comum
    • O navegador segue a resposta 3xx antes do HTMX
    • O HTMX vê apenas a resposta final após o redirecionamento e troca seu conteúdo no alvo existente
  • Em requisições HTMX, usa-se uma resposta 2xx junto com o cabeçalho HX-Redirect
    • HX-Redirect: /foo/bar faz o navegador navegar para essa URL e recarregar a página inteira
    • A requisição para o destino não inclui HX-Request: true
  • Para também dar suporte a ambientes com JavaScript desativado ou sem o HTMX carregado, requisições que não são HTMX devem continuar usando resposta 3xx normal
func redirect(w http.ResponseWriter, r *http.Request, url string, code int) {
    if isHTMXRequest(r) {
        w.Header().Set("HX-Redirect", url)
        w.WriteHeader(http.StatusNoContent)
        return
    }

    http.Redirect(w, r, url, code)
}
  • Para ir para /profile após o login, a chamada pode ser feita assim
redirect(w, r, "/profile", http.StatusSeeOther)

Diferença entre HX-Redirect e HX-Location

  • HX-Location executa um comportamento parecido com redirecionamento sem recarregar a página inteira
    • Busca o HTML da URL de destino
    • Substitui a resposta dentro de <body>
    • Adiciona uma nova entrada ao histórico do navegador
  • Ao requisitar a URL de destino, HX-Request: true é incluído
  • Isso pode causar problemas se a rota de destino retornar HTML parcial com base em HX-Request
    • O handler não consegue distinguir uma requisição vinda de HX-Location de uma requisição HTMX comum
    • A primeira precisa de página completa, enquanto a segunda precisa de resposta parcial
  • Diferentemente da restauração de histórico, não há configuração para desativar HX-Request: true em navegações via HX-Location
  • Na maioria dos casos, usar HX-Redirect é mais simples e seguro, mesmo com o custo de recarregar a página inteira
  • HX-Location também pode ser usado se a rota de destino estiver configurada para sempre retornar apenas HTML completo

Tratamento de respostas 204·422·4xx·5xx

  • Por padrão, o HTMX não troca o DOM com respostas 4xx ou 5xx
    • A tela atual permanece como está
    • O erro é registrado no console das ferramentas de desenvolvedor
  • Mesmo que o servidor retorne 500 Internal Server Error por causa de um nome de template inexistente, na configuração padrão o usuário pode não perceber o erro na tela
  • Para exibir mensagens de erro ao usuário, respostas 4xx e 5xx devem ser configuradas para substituir todo o <body>
  • A exceção é 422 Unprocessable Content
    • Ele é usado ao retornar um formulário com erros de validação
    • A resposta deve substituir normalmente o alvo HTMX existente
  • responseHandling é configurado assim, de acordo com o status code
    • 204 No Content: não altera o DOM
    • 422 Unprocessable Content: substitui normalmente o alvo existente
    • Demais 4xx e 5xx: substituem a resposta no <body>
    • Outros casos: substituem normalmente o alvo existente
"responseHandling":[
    {"code":"204", "swap": false},
    {"code":"422", "swap": true},
    {"code":"[45]..", "swap": true, "target": "body"},
    {"code":"...", "swap": true}
]
  • Se, em determinadas interações, o erro precisar ser colocado em outro alvo em vez do <body>, é possível sobrescrever o padrão com a extensão response target

Verificando a URL atual na barra de endereços do navegador

  • O HTMX não altera a URL do navegador durante o processo de requisição AJAX e substituição da resposta, a menos que hx-boost, hx-push-url ou hx-replace-url sejam usados
  • Por isso, r.URL no handler Go e a URL que o usuário vê na barra de endereços podem ser diferentes
  • O HTMX envia a URL atualmente exibida pelo navegador no cabeçalho HX-Current-URL de cada requisição
  • A função auxiliar faz o parse desse cabeçalho como url.URL e, se o cabeçalho não existir, retorna r.URL
func browserURL(r *http.Request) (*url.URL, error) {
    cu := r.Header.Get("HX-Current-URL")
    if cu != "" {
        return url.Parse(cu)
    }

    return r.URL, nil
}

Configuração que altera os padrões do HTMX

  • historyCacheSize: 0 desativa completamente o cache de páginas no armazenamento local do HTMX
    • O cache no armazenamento local pode ser uma fonte de bugs e problemas de segurança
    • Ao clicar em voltar, o HTML é buscado novamente do servidor em vez de vir do cache
    • Em versões futuras do HTMX, o cache no armazenamento local também deverá vir desativado por padrão pelo mesmo motivo
  • disableInheritance: true desativa a herança de atributos do HTMX
    • Declarar os atributos sempre de forma explícita é mais claro
    • Reduz o risco de bugs ou comportamentos não intencionais
    • Em versões futuras do HTMX, a herança de atributos também deverá vir desativada por padrão
  • includeIndicatorStyles: false faz com que o HTMX não injete estilos de indicador
    • Os estilos de indicador são definidos manualmente junto com outras regras de CSS para manter a consistência
  • As requisições do HTMX, por padrão, não têm limite de tempo e esperam até o servidor responder
    • Dependendo da forma como a aplicação Go trata timeout e deadlines, é possível definir timeout em milissegundos
    • Como ponto de partida, usa-se timeout: 5000
  • A configuração padrão também inclui historyRestoreAsHxRequest: false e responseHandling por código de status
<meta
    name="htmx-config"
    content='{
        "includeIndicatorStyles": false,
        "historyCacheSize": 0,
        "historyRestoreAsHxRequest": false,
        "responseHandling":[
            {"code":"204", "swap": false},
            {"code":"422", "swap": true},
            {"code":"[45]..", "swap": true, "target": "body"},
            {"code":"...", "swap": true}
        ],
        "timeout": 5000
    }'
>

Layouts por página para aplicações grandes

  • Se apenas o template base e os templates por página não forem suficientes, é possível adicionar um template de layout entre essas duas camadas
  • Isso é adequado para áreas como páginas administrativas, que precisam de um título, menu de navegação e estrutura de conteúdo diferentes das páginas normais
  • base chama layout em vez de chamar page:content diretamente
<body>
    {{template "layout" .}}
</body>
  • layouts/admin.tmpl define a estrutura comum da área administrativa e chama page:content internamente
    • Título da área administrativa
    • Menu de navegação para usuários e pedidos
    • <main> onde entra o conteúdo específico da página
  • O handler passa para render() tanto o layout quanto o template da página a ser usado
app.html.render(
    w,
    200,
    nil,
    "base",
    "layouts/admin.tmpl",
    "pages/admin-orders.tmpl",
)
  • Mantendo o template base compartilhado, é possível combinar layouts por rota e conteúdo de página, permitindo estender o mesmo padrão de renderização para telas que precisam de uma estrutura própria, como a área administrativa

1 comentários

 
GN⁺ 4 시간 전
Comentários do Hacker News
  • Gosta de Go + HTMX e usa junto com a-h/templ para aumentar a segurança de tipos de templates, componentes e HTML parcial Montou toda a caixa de ferramentas com Go·Unix·SQLite, chamou de stack GUS e publicou: https://housecat.com/blog/the-gus-stack-go-unix-sqlite Foi fortemente inspirado pela stack GUTS do exe.dev, mas usa HTMX no lugar de TypeScript: https://exe.dev/docs/guts Para erros com rastreamento de stack usa cockroachdb/errors, para HTML type-safe usa templ, para geração de especificações OpenAPI usa fuego, para geração de código SQL usa sqlc, para SQLite puro em Go usa modernc.org/sqlite, para migrations usa goose, para workflows persistentes usa dbos, e para testes e automação com Chrome/CDP usa rod Essa combinação é produtiva tanto para programar diretamente ou com ajuda de agentes quanto para o processo de build e deploy de um binário único

    • Ao falar de HTMX, acha uma pena que Hyperscript não receba a atenção merecida Ele permite manipular o DOM sem roundtrips ao servidor nem funções JavaScript separadas, então combina muito bem com Go + HTMX e parece uma extensão do próprio HTML em que já se está trabalhando Algumas pessoas podem não gostar de programação declarativa, mas no Open Payment Host foi possível implementar até manipulações complexas de DOM usando apenas Hyperscript https://hyperscript.org/, https://openpaymenthost.com/
    • Também daria para renomear a stack para HUGS stack, juntando HTMX ou hypertext·Unix·Go·SQLite
    • Também já viu Go·Templ·HTMX ser chamado de stack GoTH, e com sqlc incluído vira GoTHs ou GHosT Se você aceitar go generate ./... como parte da etapa de build, há muito a ganhar, e com goverter também é possível gerar conversões entre modelos do sqlc e objetos/valores de retorno de templates Cerca de 50% do boilerplate é gerado automaticamente, então a segurança de tipos fica excelente
    • Recentemente, tem gostado bastante de usar jet para SQL type-safe Ele precisa de um banco de dados em execução para gerar código, mas isso também é visto como vantagem, porque força a aplicar primeiro as migrations antes de escrever o código https://github.com/go-jet/jet
    • Para web apps reativos pequenos, como dashboards de API em tempo real, que talvez nem precisem de banco próprio, tem usado bastante ultimamente o template PAHG É uma combinação de Pico.css·Alpine.js·HTMX·Go, e claro que também dá para conectar SQLite https://github.com/hiAndrewQuinn/pahg-template
  • HTMX é excelente para vários usos, mas é muito difícil adotá-lo se os colegas de equipe não concordarem Continua havendo resistência de que não é uma tecnologia séria, e todo tipo de bug primeiro é atribuído ao HTMX; mesmo quando depois se descobre que foi um mal-entendido, a confiança já foi abalada Isso aconteceu até na melhor equipe em que já trabalhou, e a lição foi que no fim é preciso escolher quais batalhas vale a pena travar Também acha que a interface de html/template do Go é especialmente pouco natural. Como recomenda A Philosophy of Software Design, a complexidade deveria ser empurrada para dentro, mas uma estrutura em que é preciso se preocupar em duplicar templates toda vez que se renderiza HTML é desnecessariamente complexa

    • Eu também gosto de HTMX, mas na adoção em equipe vivi a mesma realidade Propus para uma web app simples, mas um desenvolvedor acostumado há anos a retornar JSON e renderizar com JS/jQuery teve dificuldade em resolver os problemas existentes de um jeito novo em cada etapa, outro desenvolvedor nem tentou, e na equipe só se falava de Blazor o tempo todo Qualquer abordagem nova provavelmente seria difícil, mas HTMX praticamente não teve chance É uma pena, porque era uma equipe pequena, e se desse para evitar 95% do JavaScript e gerar o código e o HTML no servidor, a base de código ficaria mais organizada e também mais fácil de testar. Em projetos pessoais continuo usando HTMX, encontrei um bom fluxo e sinto que o desenvolvimento web ficou muito mais simples
    • Em papel de consultoria, guiou ativamente a equipe e explicou que aprender HTMX aprofunda a compreensão do navegador, que é o ambiente real de trabalho, enquanto React e afins isolam o desenvolvedor do navegador, mas mesmo assim passou pela mesma coisa: https://www.reddit.com/r/htmx/s/DuXyGgsCWK O html/template é bom por trazer segurança básica e vir no padrão, mas por sua natureza dinâmica há partes demais sem resposta definitiva, e depois de escrever alguns apps comerciais grandes com Templ também achou a experiência de desenvolvimento aquém do ideal Por isso criou o gsx: https://github.com/gsxhq/gsx
    • Não é necessário duplicar o template toda vez que ele é renderizado; basta compilá-lo uma vez e depois só executá-lo O problema no texto original foi criado pela própria pessoa ao tentar definir o título da página no template principal. Basta passar o título pelo contexto e dividir base.tmpl em BASE_BEGIN e BASE_END para usar no template final O título específico de cada página ainda precisa ser passado em tempo de execução, mas a abordagem do texto original também acabaria quebrando quando se começasse a dar suporte a múltiplos idiomas
    • Na nossa empresa é exatamente o contrário: é impossível convencer de que React SPA não escala, porque é visto como tecnologia estabelecida apesar de todos os problemas Cada problema é analisado isoladamente, mas ninguém consegue conectá-los ao quadro maior de que todos vêm da mesma estrutura
  • Em um projeto recente, usou HTMX com grande satisfação Como alguém que conhece a web de antes de AngularJS e React, gosta especialmente de poder construir páginas reais e minimizar a quantidade de JavaScript Dá para fazer também com JavaScript puro, mas HTMX substitui aquele boilerplate repetitivo de tratamento de eventos Se você não gosta da filosofia moderna de frontend, vale a pena experimentar; os primeiros exemplos no site oficial são bem básicos, mas ao aprender um pouco mais ele se torna muito mais poderoso

    • Também gostaria de recomendar com cautela o SvelteKit Por fora ele parece só mais um framework JavaScript de frontend, mas ao usar na prática percebeu que ele dá um ótimo suporte ao design centrado em hypertext que a linha do HTMX busca
  • Já tentei criar um projeto relativamente grande com HTMX + Go, mas ainda não foi suficiente, e não tenho certeza se algum dia vai chegar ao nível que eu quero É excelente para apps CRUD simples e painéis administrativos, mas quando aumentam os componentes interligados, o estado compartilhado e as interações complexas, a manutenção rapidamente fica difícil Escolhi HTMX porque não gostava de React, mas no fim mantive o backend em Go e troquei o frontend para SvelteKit em modo SPA, conseguindo separar bem os dois lados e desenvolver/manter uma UI complexa com muito mais facilidade O Svelte parece mais uma extensão natural do HTML do que outra linguagem como JSX, o gerenciamento de estado e o modelo de componentes são simples, e a nova sintaxe $state é especialmente boa

    • O próprio site do HTMX deixa claro esse trade-off, dizendo que ele não serve para esse tipo de uso, mas é preciso cavar um pouco para achar esses textos
    • Hyperscript, projeto irmão de frontend da equipe do HTMX, lida com recursos como estado no frontend. Fico curioso se isso também foi testado: https://hyperscript.org/docs/reactivity/
    • Acho que o cerne da filosofia do HTMX é justamente questionar se componentes conectados por estado compartilhado são mesmo necessários desde o início No fim, a maioria dos apps só faz CRUD sobre um banco de dados
  • Hoje em dia, quase todos os meus side projects são feitos com Go + HTMX, com ou sem ajuda de LLM Tanto o Opus quanto o GPT lidam muito bem com essa combinação, dá para criar e começar rápido, e por ser um binário único, deploy e hospedagem também ficam convenientes É uma stack muito boa para desenvolvimento iterativo rápido

  • Se for usar HTMX, recomendo fortemente uma forma de gerar HTML no backend que facilite extrair trechos HTML comuns em funções, componentizando como se faz com React Com HTMX, o backend precisa de flexibilidade no HTML que gera, porque dependendo do caso é necessário adicionar ou remover tags ao lado de um certo HTML específico. Por exemplo, se ele encontrar uma tag `` no topo, atualiza o título da página Em mecanismos de template tradicionais baseados em string, esse tipo de coisa é difícil, mas em bibliotecas de HTML embutidas na linguagem isso é simples, e como também permitem usar todos os recursos de abstração da linguagem, combinam especialmente bem com HTMX Em backends JavaScript, você pode usar funções de tagged template literal como https://github.com/WebReflection/uhtml-ssr; em Go, https://www.gomponents.com/; em Scala, ScalaTags

  • Conheci Go pela primeira vez pelos livros do Alex Edwards e pelo Learn Go with Tests, e ainda recomendo ambos Fiquei com vontade de tentar migrar alguns projetos antigos para HTMX

    • Se você tiver qualquer interesse em desenvolvimento web com Go, precisa ler Let's Go e Let's Go Further, do Alex Edwards São os livros introdutórios de linguagem de programação mais acessíveis, agradáveis e práticos que já li
  • Criei para uso pessoal um framework baseado em Kotlin + HTMX: https://github.com/reubenfirmin/zoned O objetivo era ver se dava para tornar todo o app web type-safe de ponta a ponta, e usei o Kotlinx.html, que permite escrever HTML com uma DSL parecida com JSX Fiz os primeiros 90% por conta própria e contei com a ajuda do Claude para os 10% finais e o README, para concluir a publicação

  • Gosto tanto de Go quanto de Alex Edwards, mas meus resultados com HTMX sempre foram decepcionantes Parece que a complexidade da base de código cresce duas vezes mais rápido do que a do próprio app, e sempre acabo encontrando casos de borda dos quais não dá para sair sem algum workaround esquisito Entendo por que tanta gente não gosta de pacotes Node, mas HTMX me parece uma compensação exagerada para isso. O tempo que economizei por não brigar com JSON foi mais do que anulado por gastar 3 vezes mais tempo tentando fazer o app parecer e se comportar direito Com o template do Mantine, em 2 minutos dá para configurar e aproveitar ótimos componentes de UI, e incluindo os assets estáticos gerados também dá para manter tudo como um único binário Go: https://github.com/mantinedev/vite-min-template

    • Quando tive meu primeiro contato com hipermídia em Rails, achei conveniente e fácil, então o atrito com Go/HTMX foi uma surpresa, mas depois de várias tentativas aprendi que abstração é importante Sem um gerador de componentes e um reverse router, a dor é considerável, e no Rails isso já vem pronto, então você nem precisa pensar nisso A maior vantagem é que cálculo e dados ficam no mesmo lugar. LLMs também usam e testam bem apps de hipermídia autocontidos, e como não é preciso esperar JavaScript, o ciclo de iteração fica mais curto Ainda assim, concordo que a falta de uma biblioteca de componentes pronta para uso dificulta começar
    • Para usar HTMX, precisei mudar toda a filosofia de design Isso não é necessariamente ruim, pode até ser uma mudança positiva, mas é uma transição grande, e o resultado fica mais próximo de algo nativamente web do que de uma imitação de app mobile
    • Fico curioso sobre exemplos concretos de UI que você conseguiu fazer com Mantine, mas não com HTMX
  • Prefiro muito mais Go + Datastar por ser mais simples

    • Go e Datastar combinam perfeitamente Também gostaria de adicionar a-h/templ aos templates, e o Datastar dá suporte a isso Surpreende que o Datastar ainda seja tão pouco conhecido
    • Eu também prefiro Datastar, mas os novos recursos do HTMX4 estão reduzindo bastante a diferença É bom ver o ecossistema convergindo para padrões melhores