Suporte a HTMX na deco.cx

Libere o potencial das apis nativas da web
05/27/2024·Tiago Gimenes

Estamos entusiasmados em anunciar um novo App HTMX no Deco Hub. O HTMX tem ganhado popularidade por sua capacidade de criar aplicações web dinâmicas sem depender fortemente do JavaScript. Aproveitando os atributos HTML, o HTMX simplifica o processo de desenvolvimento, facilitando a construção de interfaces de usuário interativas.

Por que apoiar o HTMX agora?

No deco.cx, pretendemos apoiar todos os principais frameworks, incluindo Angular, Next.JS, SvelteKit e outros. No entanto, começamos com o HTMX porque vemos um grande potencial em simplificar tanto o desenvolvimento quanto a produção de páginas web simples. O HTMX se destaca em cenários onde os desenvolvedores querem evitar as complexidades dos processos de build, substituição de módulos a quente (HMR) e outras ferramentas. Com o HTMX, o que você escreve em HTML é exatamente o que você vê tanto em ambientes de desenvolvimento quanto de produção.

Combinando o HTMX com as forças do HTML5 e CSS3, os desenvolvedores podem oferecer boas experiências de usuário e desempenho de primeira classe sem o peso de baixar, analisar e compilar bibliotecas pesadas de JavaScript. Além disso, usar o HTMX pode resultar em aplicações web mais robustas. Testar JavaScript em todos os dispositivos e navegadores (Safari, navegador Samsung, etc.) é demorado e caro. Ter sua UI renderizada no servidor economiza incontáveis horas de depuração em diferentes ambientes, reduzindo, em última análise, as despesas com Sentry.

Como o deco.cx se integra com o HTMX

Se o HTMX oferece tantas vantagens, por que não é o padrão para o desenvolvimento de aplicações web? O principal desafio com o HTMX está na necessidade de criar rotas para cada estado da UI, o que pode complicar o processo de desenvolvimento. Cada elemento interativo ou atualização dinâmica frequentemente requer uma rota correspondente no servidor, levando a uma proliferação de endpoints e aumento da complexidade de manutenção.

Além disso, a web é composta principalmente por servidores centralizados, e computar novos estados da UI em um servidor centralizado pode penalizar os usuários periféricos devido a problemas de latência. Isso não acontece com o deco.cx, onde nossa infraestrutura edge-first distribui o código globalmente. Isso garante que computar novos estados da UI seja muito mais barato e rápido, graças à nossa baixa latência e infraestrutura distribuída globalmente.

Para nós, no deco.cx, a parte mais difícil de usar o HTMX é ter que criar uma rota para cada estado da UI. Para resolver esse desafio, desenvolvemos um novo hook chamado useSection. Este hook cria automaticamente rotas para renderizar seus estados da UI sem exigir que os desenvolvedores lidem manualmente com roteamento.

Para demonstrar o poder e a simplicidade do hook useSection, vamos explorar um exemplo construindo um componente de contador.

Construindo um Contador com useSection no deco.cx

Neste guia, vamos construir um componente de contador simples usando HTMX e o hook useSection no deco.cx.

preact

Versão Preact

Primeiro, vejamos o código usual do Preact para este componente usando o hook useState:

import { useState } from "preact/hooks";

export default function Section() {
const [count, setCount] = useState(0);

return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count - 1)}
>
<span>-</span>
</button>
<span>{count}</span>
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count + 1)}
>
<span>+</span>
</button>
</div>
);
}
Refatorando para HTMX

Para refatorar este componente para HTMX, seguimos três regras simples:

  1. Hooks do lado do cliente, como useState e useEffect, são removidos.
  2. Todas as variáveis iniciadas por um hook useState são colocadas nas props do componente.
  3. Manipuladores de eventos do lado do cliente, como onClick e onChange, são removidos.

Aplicando as regras 1 e 2 ao componente Section, movemos a variável count para as props do componente, deixando-nos com:

export default function Section({ count }: { count: number }) {
return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count - 1)}
>
<span>-</span>
</button>
<span>{count}</span>
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count + 1)}
>
<span>+</span>
</button>
</div>
);
}

Note que não precisamos mais da importação do useState. Para aplicar a regra número 3, precisamos remover o manipulador onClick, mas como manter a interatividade? É aí que o hook useSection é útil.

Integrando o useSection

Para implementar a funcionalidade onClick, começamos importando useSection de deco/hooks/useSection.ts. Este hook permite criar um link para a instância atual da seção e substituir quaisquer props que esta seção recebe. Refatorando este componente, obtemos:

import { useSection } from "deco/hooks/useSection.ts";

export default function Section({ count = 0 }:{ count: number }) {
return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
hx-get={useSection({ props: { count: count - 1 } })}
hx-target="closest section"
hx-swap="outerHTML"
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span>-</span>
</button>
<span>{count}</span>
<button
hx-get={useSection({ props: { count: count + 1 } })}
hx-target="closest section"
hx-swap="outerHTML"
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span>+</span>
</button>
</div>
);
}
Explicação

Vamos dissecar cada parte começando pelo atributo hx-get. useSection({ props: { count: count - 1 }}) cria um link para a instância atual da seção, mas substitui a prop count. Isso significa que, se tivéssemos outras props, o novo valor de count seria mesclado com os outros valores de prop, e um link para esta seção seria retornado. Isso é benéfico, pois o desenvolvedor pode agora substituir algumas props inseridas pelo CMS deco.cx.

A combinação hx-target e hx-swap é útil quando você quer substituir toda a seção no deco.cx, já que as seções são renderizadas sob um elemento <section/>.

htmx

Lidando com Conexões Lentas

Para conexões 3G, onde realizar uma solicitação para aumentar/diminuir o contador pode ser lento, precisamos adicionar um estado de carregamento. O HTMX fornece a classe indicadora htmx-request para este propósito. Quando o HTMX está realizando uma solicitação, a classe htmx-request é adicionada ao DOM. Com o TailwindCSS v3, podemos criar regras específicas ativadas quando esta classe está presente. Aqui está o botão atualizado:

<button
hx-target="closest section"
hx-swap="outerHTML"
hx-get={useSection({ props: { count: count - 1 } })}
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span class="inline [.htmx-request_&]:hidden">-</span>
<span class="hidden [.htmx-request_&]:inline loading loading-spinner" />
</button>

A mágica está no seletor [.htmx-request_&]:. Isso diz ao Tailwind para criar um seletor que é ativado sempre que a classe htmx-request estiver presente em um elemento pai. Quando realizando uma solicitação, o CSS esconderá o elemento - e exibirá um spinner. Esta abordagem aproveita as APIs nativas da Web para melhorar a experiência do usuário, mesmo em conexões lentas. Aqui está o resultado final:

htmx+loading

Avisos

Ao usar o hook useSection, lembre-se de que as props passadas para o hook vão para a URL final. Tenha cuidado com os limites de tamanho de URL e tente passar cargas pequenas, como booleanos e IDs.

Conclusão

Temos uma referência de API useSection disponível para quem quiser se aprofundar.

Além disso, fornecemos uma receita para migrar do Preact para o HTMX e vice-versa, detalhando padrões de design comuns e como alcançá-los com HTML5 e CSS3. Com essas ferramentas, os desenvolvedores podem aproveitar o poder do HTMX e a flexibilidade do deco.cx para construir aplicações web robustas e de alto desempenho.