服务器与 SSR 实战方案
在 Node 中使用 WU 渲染 HTML、React 和 Vue;检查转义、状态、CSP、错误和真实 HTTP 响应。
从 renderer 到 HTTP 响应
WU 服务器接收具有 render(props) 的模块,返回微应用标记。HTTP 宿主负责加载数据、选择可信模块和构建响应。使用 wu-framework/server 无需初始化浏览器运行时。
项目在 examples/ssr/render-page.mjs 中提供完整方案。它组合 HTML、React 和 Vue 三种真实 renderer,生成含 Declarative Shadow DOM、水合状态和 CSP 响应头的文档。
你可以下载可运行方案,解压 ZIP 后执行 npm install。其中包含代码、测试和独立 manifest,可在门户之外复现。
node examples/ssr/render-page.mjs Ada
node examples/ssr/server.mjs
第一条命令将 HTML 写入 stdout。第二条在 http://127.0.0.1:4310/?name=Ada 提供服务。命令从已安装依赖的项目根目录运行。服务器接受 PORT 环境变量,按 Ctrl+C 关闭。
一个小型 HTML renderer
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' },
});
完整容器位于 result.html 中,结构如下:
<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 包含容器内部内容;result.containerAttrs 让 Astro 或其他引擎构建外层元素。state 仅包含 renderer 显式返回的内容。自定义 HTML renderers 决定发布哪些数据;React 和 Vue helpers 将 props 作为状态返回。
无需编译 JSX 或 SFC 的 React 与 Vue
此方案使用 React.createElement 和 h,因此 Node 可以直接执行。在组件项目中,可以导入对应的服务器构建结果。
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' } },
]);
renderer 注入保持使用与宿主相同的 React 或 Vue 实例。helpers 的公共导入路径是 wu-framework/server 和 wu-framework/server/adapters;包的 exports 映射中不存在 wu-framework/server/react 或 wu-framework/server/vue subpaths。
每个响应的状态与 CSP
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 });
在同一响应的样式、状态脚本和 CSP 响应头中使用同一个 nonce;为下一个响应生成新的 nonce。renderStateScript 默认发布 window.__WU_SSR_STATE__。各集成分别输出脚本时,其 merge: true 选项可保留其他应用的条目。
本地 HTTP 方案使用 securityHeaders({ nonce, https: false, hsts: false }),因为它通过 HTTP 监听 loopback。HTTPS 服务器应配置对应传输策略。该 helper 不会启动 TLS 或管理证书。
不要在状态中发布秘密:响应 HTML 和水合 props 对客户端可见。
转义不等于清洗文档
escapeHtml 转换文本值中的特殊字符。serializeState 保持 JSON 有效,并处理 <、>、U+2028 和 U+2029,防止数据提前关闭状态脚本。测试将 </script><img ...> 作为名称传入,并检查不会出现可执行标签。
render() 返回的 HTML 会原样插入。如果收到外部 HTML,在交给 renderer 前应按产品适用的策略清洗;renderApp 不会移除其中危险的标签或属性。renderer 的 CSS 也属于可信代码。
状态必须可序列化为 JSON。循环引用和 BigInt 会使 serializeState 失败;函数和 undefined 属性不能构成可移植的水合状态。
部分失败与 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 即使并行渲染也会保留输入顺序。不使用 failFast 时,它返回正常应用的 HTML、失败应用的空容器,以及 errors 列表。宿主决定返回 200 并显示降级结果,还是更改 HTTP 状态。
使用 failFast: true 时,Promise 拒绝。但这不会自动取消其他已经开始的 renderers;应避免在 render 内执行带外部副作用的工作。
| 故障 | 结果 |
|---|---|
| 名称无效 | 渲染前拒绝;最多 50 个字符,以字母或数字开头,其余允许字母、数字、_ 或 - |
缺少 module |
提示需要 renderer 的错误 |
模块没有 render |
解析错误 |
render 抛出异常 |
包含 code: WU_SSR_RENDER_FAILED、appName 和 cause 的错误 |
shadowMode 不是 open/closed |
选项错误 |
Nonce 或 varName 无效 |
输出对应属性或脚本前报错 |
render 返回不支持的结果 |
规范化错误;不一定具有 renderer 失败错误码 |
完成水合还需要什么
该方案无需下载应用 JavaScript 即可生成真实 HTML。要实现交互,在客户端注册相同应用,并将其挂载到 SSR 容器。具有 hydrate 的生命周期接管已有 DOM;否则 WU 使用 mount。React 和 Vue adapters 必须收到与服务器相同的树和 props。
shadowMode: 'closed' 适用于静态标记:常规水合无法访问该根节点。declarative: false 生成带 data-wu-ssr="flat" 的平面标记,适用于不需要声明式封装的场景。
参阅 SSR 与水合以及生命周期,连接服务器与客户端。WASM/WASI 模块可通过宿主 runner 为 renderer 提供可移植逻辑;它本身不会替代 HTTP 服务器、数据解析或响应策略。通过 WU 连接服务指南演示浏览器中应用间的请求、契约与取消。
执行检查
node scripts/verify-server-examples.mjs
测试套件检查三种 renderers、nonce 与 CSP 一致性、状态、转义、无效选项、故障错误码和上下文、部分降级以及平面输出。测试使用已安装包的 exports,不复制其函数实现。