Ir para o conteúdo

Arquitetura

O uiify tem cinco camadas, cada uma publicada separadamente, sem importar a camada acima. Uma regra orienta todas elas: o componente precisa funcionar antes da hidratação.

O intervalo até a hidratação

O SSR com streaming deixa um intervalo entre a primeira renderização na tela e o fim da hidratação. Com Suspense, cada fronteira que chega depois tem seu próprio intervalo. Nesse período, um acordeão com useState fica visível, mas inerte; um com <details> funciona desde a primeira renderização na tela.

Dois documentos renderizam o mesmo acordeão sem nenhuma tag <script>: um com `<details>` e outro com `useState`. Abra os dois: o primeiro abre; o segundo, não. Confira o código-fonte para confirmar.

A meta é eliminar renderizações desnecessárias, atualizações que se propagam por todo o provedor e trabalho síncrono de layout causado pelo JavaScript da biblioteca — não eliminar toda renderização, já que o estado precisa ser renderizado em algum lugar. Por padrão, não há JavaScript; ele é mínimo e usado apenas onde a acessibilidade exige.

Tiers

Cada componente declara seu tier em um manifesto lido pelo build. O manifesto define o limite de tamanho, o lint de pureza RSC, a suíte Playwright e o selo de tier em cada página de componente.

TierLimiteSem JavaScriptO que significa
Tier 0
RSC — sem JavaScript no cliente
0 kBPassa integralmente na suíte sem JavaScript.O estado fica em um elemento nativo, ou não há estado. Todos os hooks React são proibidos, inclusive useMemo e useRef.
Tier 1
Estrutura RSC + pequena ilha no cliente
≤ 1 kBComportamento reduzido, documentado por componente.O elemento nativo continua sendo responsável pelo estado. O JavaScript apenas sincroniza ARIA ou acrescenta o que falta à plataforma, como intenção de passar o ponteiro ou fechamento automático.
Tier 2
Componente no cliente — SSR antes da hidratação
por componenteRenderiza marcação útil antes da hidratação.Uma única entrada de cliente. O servidor emite marcação baseada em elementos nativos sempre que possível; navegação por teclado, filtragem e sincronização de ARIA aguardam a hidratação.

Os tiers também ajudam na migração: os critérios são ativados por componente, tornando a arquitetura mais rigorosa um componente por vez.

A pilha de camadas

@gunkin/uiify/hooks       SSR-safe React hooks — the substrate
@gunkin/uiify/core        headless behaviour: focus scope, dismiss layers, anchoring
@gunkin/uiify/elements    native-mechanism wrappers — zero client JavaScript
@gunkin/uiify/components  unstyled components, RSC by default
@gunkin/uiify/styles      optional CSS design layer

Camadas inferiores nunca importam as superiores. @gunkin/uiify/elements é a mais restrita: só pode importar @gunkin/uiify/core/render e tipos do React.

Por que elements é uma camada pública

A maioria das aplicações nunca importa @gunkin/uiify/elements diretamente. A camada é publicada por dois motivos.

Primeiro, ela permite verificar o projeto. Quando uma página de componente diz _construído sobre elements/dialog_, a página desse elemento mostra a marcação exata e o contrato data-* que o sustentam.

Segundo, a composição: se a anatomia do componente não atende ao seu design, construa a sua sobre as mesmas conexões, em vez de reimplementar o mecanismo.

Um módulo só entra na camada se cumprir as cinco regras: um mecanismo nativo, nenhum comportamento em JavaScript, dois ou mais consumidores em @gunkin/uiify/components, estado legível por um seletor CSS e menos marcação duplicada, em vez de mais indireção.

Os módulos recebem nomes de recursos da plataforma — details, dialog, popover, invoker —, não de conceitos de UI. Um componente nunca tem o mesmo nome de um elemento; por isso, Modal é construído sobre dialog. Explore os elementos

Por que o contexto do React é proibido no Tier 0

Mover o estado para o DOM não adianta se as partes ainda se comunicam pelo contexto do React. Server Components não podem usar contexto; portanto, um Root e um Item que compartilham um provedor não podem ser renderizados no servidor, por mais nativa que seja sua marcação.

As conexões precisam sobreviver à serialização. Por isso, as partes se conectam por HTML: <details name> para agrupamento, commandfor e popovertarget para referências resolvidas pelo navegador e ids derivados de props. Um valor de useId não chega às partes irmãs sem contexto.

<Accordion id="faq">
  <Accordion.Item value="shipping">   {/* id="faq-shipping" */}

O custo: a raiz precisa de um id. Quando a composição não é necessária, o componente recebe dados e renderiza a árvore por conta própria. Veja Server Components