WU / DOCSMICROFRONTENDS UNIVERSAIS
PT

Receitas de servidor e SSR

Renderize HTML, React e Vue com WU no Node; verifique escape, estado, CSP, erros e respostas HTTP reais.

De um renderizador a uma resposta HTTP

O servidor do WU recebe um módulo com render(props) e retorna o markup de uma microapp. O host HTTP mantém a responsabilidade de carregar dados, escolher módulos confiáveis e construir a resposta. Você não precisa inicializar o runtime do navegador para usar wu-framework/server.

O projeto inclui uma receita completa em examples/ssr/render-page.mjs. Ela compõe três renderizadores reais — HTML, React e Vue — e gera um documento com Declarative Shadow DOM, estado de hidratação e cabeçalhos CSP.

Você pode baixar as receitas executáveis, extrair o ZIP e executar npm install. Ele inclui o código, os testes e um manifesto independente para reproduzi-los fora do portal.

node examples/ssr/render-page.mjs Ada
node examples/ssr/server.mjs

O primeiro comando escreve HTML em stdout. O segundo abre http://127.0.0.1:4310/?name=Ada. Os comandos são executados na raiz do projeto com suas dependências instaladas. O servidor aceita a variável PORT e é fechado com Ctrl+C.

Um renderizador HTML pequeno

import { escapeHtml, renderApp } from 'wu-framework/server';

const greeting = {
  render({ name }) {
    return {
      html: `<p>Hola, ${escapeHtml(name)}.</p>`,
      styles: 'p { color: #b7d900; }',
      state: { name },
    };
  },
};

const result = await renderApp('greeting', {
  module: greeting,
  props: { name: 'Ada' },
});

O contêiner completo está em result.html. Seu formato é:

<div id="wu-app-greeting" data-wu-app="greeting" data-wu-ssr="open">
  <template shadowrootmode="open">
    <style>p { color: #b7d900; }</style>
    <div data-wu-root><p>Hola, Ada.</p></div>
  </template>
</div>

result.inner contém o interior do contêiner; result.containerAttrs permite ao Astro ou a outro motor construir o elemento externo. state contém apenas o que foi retornado explicitamente pelo renderizador. Renderizadores HTML personalizados decidem quais dados publicar; os helpers React e Vue retornam as props como estado.

React e Vue sem compilar JSX nem SFC

Esta receita usa React.createElement e h para que o Node possa executá-la diretamente. Em um projeto de componentes, você pode importar o resultado do build de servidor equivalente.

import * as React from 'react';
import { renderToString as reactToString } from 'react-dom/server';
import { createSSRApp, h } from 'vue';
import { renderToString as vueToString } from '@vue/server-renderer';
import { renderReact, renderVue, renderApps } from 'wu-framework/server';

const reactApp = renderReact(
  ({ name }) => React.createElement('p', null, `Hola, ${name}`),
  { react: React, renderToString: reactToString },
);
const vueApp = renderVue(
  { props: ['name'], setup: props => () => h('p', `Hola, ${props.name}`) },
  { createSSRApp, renderToString: vueToString },
);

const result = await renderApps([
  { name: 'react-greeting', module: reactApp, props: { name: 'Ada' } },
  { name: 'vue-greeting', module: vueApp, props: { name: 'Ada' } },
]);

A injeção do renderizador mantém a mesma instância de React ou Vue usada pelo host. Os imports públicos dos helpers são wu-framework/server e wu-framework/server/adapters; não existem subcaminhos wu-framework/server/react ou wu-framework/server/vue no mapa de exportações do pacote.

Estado e CSP por resposta

import { createNonce, renderApps, renderStateScript, securityHeaders } from 'wu-framework/server';

const nonce = createNonce();
const rendered = await renderApps(apps, { nonce, failFast: true });
const headers = securityHeaders({ nonce });
const body = rendered.html + renderStateScript(rendered.state, { nonce });

Use o mesmo nonce nos estilos, no script de estado e no cabeçalho CSP dessa resposta; gere um novo para a próxima. renderStateScript publica window.__WU_SSR_STATE__ por padrão. Sua opção merge: true mantém entradas de outras apps quando cada integração emite seu próprio script.

A receita HTTP local usa securityHeaders({ nonce, https: false, hsts: false }) porque escuta por HTTP em loopback. Para um servidor HTTPS, configure a política de transporte correspondente. O helper não inicia TLS nem administra certificados.

Não publique segredos no estado: o HTML da resposta e as props de hidratação são visíveis ao cliente.

Escapar não é sanitizar um documento

escapeHtml converte os caracteres especiais de um valor de texto. serializeState mantém JSON válido e neutraliza <, >, U+2028 e U+2029 para impedir que um dado feche o script de estado. O teste introduz </script><img ...> como nome e verifica que nenhuma tag executável aparece.

O HTML retornado por render() é inserido como está. Se receber HTML externo, sanitize-o antes de entregá-lo ao renderizador, com uma política adequada ao produto; renderApp não remove tags nem atributos perigosos desse HTML. O CSS do renderizador também pertence ao código confiável.

O estado deve ser serializável como JSON. Referências circulares e BigInt falham em serializeState; funções e propriedades undefined não constituem estado de hidratação portátil.

Falhas parciais e failFast

import { renderApps } from 'wu-framework/server';

const result = await renderApps([
  { name: 'healthy', module: { render: () => '<p>Disponible</p>' } },
  { name: 'optional', module: { render() { throw new Error('Sin datos'); } } },
]);

console.log(result.errors[0].app); // optional
console.log(result.byApp.optional);
// <div id="wu-app-optional" data-wu-app="optional"></div>

renderApps mantém a ordem das entradas mesmo renderizando em paralelo. Sem failFast, retorna o HTML das apps saudáveis, contêineres vazios para as falhas e uma lista errors. O host decide se responde 200 com uma degradação visível ou altera o status HTTP.

Com failFast: true, a promise rejeita. Isso não cancela automaticamente os outros renderizadores já iniciados; evite trabalho com efeitos externos dentro de render.

Falha Resultado
Nome inválido Rejeição antes de renderizar; máximo de 50 caracteres, início alfanumérico e restante alfanumérico, _ ou -
Falta module Erro que solicita o renderizador
Módulo sem render Erro de resolução
render lança erro Erro com code: WU_SSR_RENDER_FAILED, appName e cause
shadowMode diferente de open/closed Erro de opções
Nonce ou varName inválidos Erro antes de emitir o atributo ou script correspondente
Resultado de render não aceito Erro de normalização; não tem necessariamente o código de falha do renderizador

O que falta para hidratar

A receita produz HTML real sem baixar JavaScript das apps. Para torná-lo interativo, registre as mesmas apps no cliente e monte-as nos contêineres SSR. Um ciclo de vida com hydrate adota o DOM existente; sem ele, o WU usa mount. Os adaptadores React e Vue devem receber a mesma árvore e as mesmas props do servidor.

shadowMode: 'closed' serve para markup inerte: a hidratação normal não pode acessar essa raiz. declarative: false produz markup plano com data-wu-ssr="flat", útil quando você não precisa de encapsulamento declarativo.

Consulte SSR e hidratação e o ciclo de vida para conectar os dois lados. Um módulo WASM/WASI pode fornecer lógica portátil ao renderizador por um runner do host; sozinho, não substitui o servidor HTTP, a resolução de dados nem a política de resposta. O guia Um serviço conectado com WU percorre solicitações, contratos e cancelamento entre aplicações no navegador.

Executar as verificações

node scripts/verify-server-examples.mjs

A suíte verifica os três renderizadores, a correspondência entre nonce e CSP, estado, escape, opções inválidas, código/contexto de falhas, degradação parcial e saída plana. Os testes usam as exportações do pacote instalado, não cópias das suas funções.

CONSTRUA O WU CONOSCO

Encontrou um bug? Tem uma ideia?

Sua experiência melhora o WU. Conte o que falhou ou ajude a manter seu desenvolvimento.