SSR 与水合
服务器 render 契约、Declarative Shadow DOM、水合状态及 React/Vue helpers。
版本、变更与要求见 Framework 3.2 与 CLI 1.0。
服务器生成什么
wu-framework/server 将 renderer 结果转换为 WU 容器的 HTML。默认使用 Declarative Shadow DOM 封装内容和样式。浏览器可以在应用 JavaScript 加载前创建该树。
服务器接收具有 render(props) 的模块,而不是客户端挂载生命周期。结果可以是 HTML 字符串,也可以是 { html, styles?, state? }。HTML 生成方必须在加入不可信数据前进行转义。
import { renderApp, escapeHtml, renderStateScript } from 'wu-framework/server';
const result = await renderApp('greeting', {
props: { name: 'Ada' },
module: {
render(props) {
return {
html: `<p>Hola, ${escapeHtml(props.name)}</p>`,
styles: 'p { color: #f97316; }',
state: props,
};
},
},
});
const html = result.html + renderStateScript({ greeting: result.state });
renderApp 还接受已导入模块、Promise、default 模块或加载模块的函数。宿主决定服务器上可执行哪些模块;不要把用户输入转换为任意 import。
结果与选项
| 项目 | 行为约定 |
|---|---|
result.html |
完整容器 |
result.inner |
声明式或平面内部内容 |
result.containerAttrs |
ID、data-wu-app、data-wu-ssr、可选 class |
result.state |
renderer 返回的可选状态 |
containerId |
默认为 wu-app-<name> |
shadowMode |
默认为 open;closed 阻止常规水合 |
declarative |
默认为 true |
styles |
宿主为该应用添加的 CSS |
nonce |
此响应样式的 CSP nonce |
renderApps 并行处理多个应用。默认将单个应用的故障记录在 errors 中,并保留其他结果;failFast: true 允许提前拒绝。renderer 在 renderApp 中失败会产生错误码为 WU_SSR_RENDER_FAILED 的错误。
保留 DOM 完成水合
客户端 shell 正常注册应用,并挂载到 SSR 容器。sandbox 检测 data-wu-ssr 和 data-wu-root 根节点。如果存在 lifecycle.hydrate,core 会传入服务器 props 和 scoped facade 并调用它。没有该 slot 时调用 mount;原有 HTML 作为初始内容保留,直到 renderer 替换它。
wu.define('greeting', {
mount(container) {
container.textContent = 'Montaje cliente';
},
hydrate(container, { props }) {
// Adoptar lo que ya existe. No limpiar container primero.
const paragraph = container.querySelector('p');
paragraph?.setAttribute('aria-label', `Saludo para ${props.name}`);
},
unmount(container) {
container.replaceChildren();
},
});
对于 React,hydrateRoot 必须接收与服务器生成内容相同的树和 props。对于 Vue,adapter 使用 createSSRApp 和 .mount 接管已有 DOM。发布的状态必须保持这种对应;渲染与水合之间改变数据会导致不匹配。
Renderer helpers
server subpath 导出 renderReact、renderPreact、renderVue、renderSvelte、renderSolid 和 renderHtml。这些 helpers 返回符合 render 契约的模块。
import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { renderReact } from 'wu-framework/server';
import App from './App.jsx';
export default renderReact(App, {
react: React,
renderToString,
styles: '.greeting { font-weight: 600; }',
});
注入 renderer 和运行时可避免在服务器 bundle 外解析另一份 React。Vue 支持注入 createSSRApp 和 renderToString,以及 setup。服务器选项名称与客户端 adapter 的 runtime: { React, ReactDOM } 不完全相同。
Astro
---
import WuAppSSR from 'wu-framework/integrations/astro/WuAppSSR.astro';
import greetingServer from '../apps/greeting/server.js';
---
<WuAppSSR
name="greeting"
module={greetingServer}
props={{ name: 'Ada' }}
fallbackToClient={false}
/>
shell 仍需初始化对应的客户端注册。组件将状态发布为 JSON 以进行水合;hydrate 默认为 true。fallbackToClient 也默认为 true:SSR 失败会留下 slot 供客户端挂载,除非像示例中一样禁用它。
在 Astro 静态构建中,renderer 在编译时运行;这不会让页面按请求生成个性化内容。依赖用户的数据需要合适的服务器策略或客户端加载。
状态与响应头
serializeState 和 renderStateScript 处理 HTML/script 上下文的序列化。escapeHtml 转义文本,不验证任意 HTML 应用。不要将秘密注入水合状态:页面上发布的所有内容对读者可见。
createNonce() 为一个响应生成 nonce。securityHeaders、applySecurityHeaders、wuSecurityMiddleware 和 withSecurityHeaders 帮助安装 CSP 和其他响应头。需要执行的样式与脚本应使用同一响应 nonce,下个响应使用新 nonce;服务器负责协调。
实现参考
导出:src/server/index.js;render:src/server/wu-server-render.js;多应用渲染:src/server/wu-server-render.js;类型:src/server/index.d.ts;DOM 接管:src/core/wu-sandbox.js;hydrate slot:src/core/wu-core.js;Astro:integrations/astro/WuAppSSR.astro。测试:tests/unit/server-render.test.js、tests/unit/server-security-fuzz.test.js、tests/unit/security-headers.test.js。
要隔离每个请求并限制工作,请查阅 SSR 参考中的 createServerRequest、私有 loaders、取消和并发。4.0 响应头 wrappers 拒绝固定 nonce:请使用每次请求调用的函数或默认生成方式。