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.