渲染与 HTTP 安全参考
SSR、服务器适配器、序列化和响应头的完整约定。
服务器与浏览器各有职责
从 wu-framework/server、wu-framework/server/adapters 或 wu-framework/server/security 导入这些函数。渲染接收显式服务器模块,不挂载浏览器组件,也不自动下载 manifest 入口。
每个请求必须保持自己的状态和 nonce。renderer HTML 属于可信内容,外部数据用 escapeHtml 处理,不要通过 renderStateScript 发送秘密。服务器示例展示响应与 hydration 的组合。
本页 RenderResult 和 RenderOutput 名称描述结果形状;要导入实际公开的类型,请检查 server 子路径声明。
renderApp
用服务器模块渲染应用,生成 WU hydration 可识别的容器。
renderApp(appName, options): Promise<RenderResult>
- 参数: 非空名称;options.module 必填,props、styles、containerId、className、shadowMode 和 declarative;nonce 可选。
- 返回值: 包含 html、inner、containerAttrs、state、appName 和 containerId 的对象。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderApp, renderHtml, escapeHtml } from 'wu-framework/server';
const card = renderHtml(({ title }) => `<h2>${escapeHtml(title)}</h2>`);
const result = await renderApp('card', { module: card, props: { title: 'Resumen' } });
console.log(result.html);
模块必须产生可信 HTML。helper 转义属性和状态,但不净化 render 返回的 HTML,也不自动导入浏览器 URL。
参考:src/server/wu-server-render.js.
renderApps
并行渲染列表,汇总 HTML、状态和各应用错误。
renderApps(apps, options?): Promise<RenderAppsResult>
- 参数: {name, module, …options} 列表;failFast 默认 false;可选共享 nonce。
- 返回值: {html, byApp, state, errors};failFast false 保留部分结果。
- 错误: 默认按条目记录错误;failFast:true 拒绝整个操作,但不取消已运行的 renderers。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderApps, renderHtml } from 'wu-framework/server';
const result = await renderApps([
{ name: 'summary', module: renderHtml(() => '<p>Resumen</p>') },
{ name: 'activity', module: renderHtml(() => '<p>Actividad</p>') },
]);
console.log(result.errors);
参考:src/server/wu-server-render.js.
renderStateScript
将状态序列化到 script,客户端 hydration 时可恢复。
renderStateScript(state, options?): string
- 参数: 可序列化 state;JavaScript 标识符 varName、布尔 merge、可选 nonce。
- 返回值: 完整 script 标签。
- 错误: 无效 varName、无效 nonce 或 JSON.stringify 无法序列化的值会被拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderStateScript } from 'wu-framework/server';
const script = renderStateScript({ card: { title: 'Resumen' } }, { merge: true });
不要放入秘密,此状态在响应 HTML 中可见。
参考:src/server/wu-server-render.js.
declarativeShadowDomPolyfill
缺少原生支持时,为带 shadowrootmode 的 templates 生成兼容 script。
declarativeShadowDomPolyfill(options?): string
- 参数: 可选 options.nonce。
- 返回值: script HTML;调用 helper 不会执行它。
- 错误: 无效 nonce 在构造属性时失败。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { declarativeShadowDomPolyfill } from 'wu-framework/server';
const script = declarativeShadowDomPolyfill({ nonce });
script 应输出在待转换内容之后;它不是观察未来 templates 的 observer。
参考:src/server/wu-server-render.js.
escapeHtml
转义 &、标签符号和引号,供 HTML 文本或属性使用。
escapeHtml(value): string
- 参数: 任意值;null 和 undefined 转为空字符串。
- 返回值: 转义后的字符串。
- 错误: 字符串转换可能传播值自身的错误。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { escapeHtml } from 'wu-framework/server';
escapeHtml('<etiqueta>'); // '<etiqueta>'
不验证 URL,也不把任意 HTML 转为安全内容。
参考:src/server/wu-server-render.js.
serializeState
编码 JSON,通过转义标签字符防止提前关闭 script。
serializeState(value): string
- 参数: JSON 兼容值;根 null/undefined 表示为 null。
- 返回值: 转义 <、> 和 Unicode 分隔符的 JSON。
- 错误: 循环引用、BigInt 或不支持的序列化可能抛出。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { serializeState } from 'wu-framework/server';
serializeState({ title: '</script>' });
参考:src/server/wu-server-render.js.
SSR_MARKER_ATTR / SSR_ROOT_ATTR
暴露协调 renderer 与 hydration 的属性名称。
SSR_MARKER_ATTR: 'data-wu-ssr'; SSR_ROOT_ATTR: 'data-wu-root'
- 参数: wu-framework/server 导出的常量。
- 返回值: 两个字符串。
- 错误: 读取常量不验证容器内容。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { SSR_MARKER_ATTR, SSR_ROOT_ATTR } from 'wu-framework/server';
console.log(SSR_MARKER_ATTR, SSR_ROOT_ATTR);
参考:src/server/wu-server-render.js.
createNonce
通过 crypto.getRandomValues 生成 128 位 nonce。
createNonce(): string
- 参数: 无参数。
- 返回值: 无 padding 的 base64url 字符串。
- 错误: 需要 Web Crypto,不可用时抛出。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { createNonce } from 'wu-framework/server';
const nonce = createNonce();
参考:src/server/wu-security-headers.js.
buildCsp
组合 scripts、styles、images、connections 和 frames 的 CSP 策略。
buildCsp(options?): string
- 参数: nonce;scriptSrc/styleSrc/connectSrc/imgSrc/frameSrc 列表;unsafeInlineStyle;https 默认 true。
- 返回值: 完整 Content-Security-Policy 值。
- 错误: 拒绝无效 nonce;origin 列表必须来自可信配置。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { buildCsp } from 'wu-framework/server';
const policy = buildCsp({ nonce, connectSrc: ['https://api.example.com'] });
没有 nonce 和 styleSrc 时,策略为样式加入 unsafe-inline;connect-src 也包含 scriptSrc。https:false 省略 upgrade-insecure-requests。
参考:src/server/wu-security-headers.js.
securityHeaders
构造 CSP、HSTS、X-Frame-Options、MIME、referrer、permissions 和可选 CORS 响应头。
securityHeaders(options?): Record<string, string>
- 参数: CSP 选项,以及 hsts、frameOptions、referrerPolicy、permissionsPolicy、nosniff、corsOrigin 和 csp。
- 返回值: 响应头映射,不修改响应。
- 错误: 生成策略时传播 CSP 验证错误。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { securityHeaders } from 'wu-framework/server';
const headers = securityHeaders({ nonce, hsts: false, corsOrigin: 'https://shell.example.com' });
HSTS 默认启用;开发 HTTP 响应应显式关闭。csp:false 省略 CSP。
参考:src/server/wu-security-headers.js.
applySecurityHeaders
向兼容 setHeader 的 Node 响应应用响应头。
applySecurityHeaders(res, options?): response
- 参数: 含 setHeader 的 res;getHeader 用于合并现有 Vary。
- 返回值: 同一响应。
- 错误: res 缺少 setHeader 或策略无效时抛出。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { applySecurityHeaders } from 'wu-framework/server';
applySecurityHeaders(res, { nonce });
参考:src/server/wu-security-headers.js.
wuSecurityMiddleware
创建每请求 nonce 的 Express 或 Connect 中间件。
wuSecurityMiddleware(options?): middleware
- **参数:**响应头选项;
nonce必须是每次请求调用的函数。省略时自动生成新 nonce;构造 wrapper 时会拒绝固定字符串。 - 返回值: (req,res,next) 函数,写入 req.wuNonce,存在 locals 时写入 res.locals.nonce。
- 错误: 执行中间件时传播 nonce 或响应头生成错误。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { wuSecurityMiddleware } from 'wu-framework/server';
app.use(wuSecurityMiddleware());
参考:src/server/wu-security-headers.js.
withSecurityHeaders
包装 Fetch handler,添加响应头并保留响应体和 HTTP 状态。
withSecurityHeaders(handler, options?): (request) => Promise<Response>
- 参数: handler(request,{nonce}) 必须返回 Response。
- 返回值: 返回新 Response 的异步 handler。
- 错误: 传播 handler 错误,并要求结果兼容 Response。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { withSecurityHeaders } from 'wu-framework/server';
const GET = withSecurityHeaders(async (_request, { nonce }) => {
return new Response(`<p>Listo</p>`, { headers: { 'Content-Type': 'text/html' } });
});
参考:src/server/wu-security-headers.js.
renderReact
将 React renderer 适配为 renderApp 契约。
renderReact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: React Component;可注入 styles、strictMode、react 和 renderToString。
- 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderReact, renderApp } from 'wu-framework/server';
import React from 'react';
import { renderToString } from 'react-dom/server';
const module = renderReact(Card, { react: React, renderToString });
const result = await renderApp('card', { module, props: { count: 2 } });
注入与组件相同的 React 实例,避免重复 runtime。
参考:src/server/adapters.js.
renderVue
将 Vue renderer 适配为 renderApp 契约。
renderVue(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: Vue Component;可注入 styles、setup(app,props)、createSSRApp 和 renderToString。
- 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderVue, renderApp } from 'wu-framework/server';
const module = renderVue(Card, { setup(app) { /* app.use(plugin) */ } });
const result = await renderApp('card', { module, props: { count: 2 } });
需要 vue 和 @vue/server-renderer,或注入两个函数。
参考:src/server/adapters.js.
renderPreact
将 Preact renderer 适配为 renderApp 契约。
renderPreact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: Preact Component;可注入 styles、h 和 renderToString。
- 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderPreact, renderApp } from 'wu-framework/server';
const module = renderPreact(Card, { styles: ['h2{color:navy}'] });
const result = await renderApp('card', { module, props: { count: 2 } });
未注入函数时,需要 preact 和 preact-render-to-string。
参考:src/server/adapters.js.
renderSvelte
将 Svelte renderer 适配为 renderApp 契约。
renderSvelte(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: 为服务器编译的组件,以及可选 styles。
- 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderSvelte, renderApp } from 'wu-framework/server';
const module = renderSvelte(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
Svelte 4 使用 Component.render;Svelte 5 导入 svelte/server。wrapper 不向文档添加 out.head。
参考:src/server/adapters.js.
renderSolid
将 Solid renderer 适配为 renderApp 契约。
renderSolid(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: 为 SSR 编译的组件,以及可选 styles。
- 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderSolid, renderApp } from 'wu-framework/server';
const module = renderSolid(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
在服务器环境解析 solid-js/web,不像 React 那样接受注入 renderer。
参考:src/server/adapters.js.
renderHtml
将 HTML renderer 适配为 renderApp 契约。
renderHtml(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- 参数: (props) => string|Promise
函数,以及字符串/列表/函数形式的 styles。 - 返回值: 带 render 的服务器模块,返回 html、styles 和 state。
- 错误: 传播输入或 renderer 错误;发送 HTTP 响应前处理拒绝。
- 权限: 不使用浏览器 grants;服务器应用控制模块、数据和响应头。
- 清理: 不创建 listeners 或持久客户端 runtime。
import { renderHtml, renderApp } from 'wu-framework/server';
const module = renderHtml(({ count }) => `<p>${Number(count)}</p>`);
const result = await renderApp('card', { module, props: { count: 2 } });
返回 HTML 不经过净化,插值前请转义外部内容。
参考:src/server/adapters.js.
createServerRequest
createServerRequest(options?): WuServerRequest
创建每次请求私有的上下文,包含 signal、locals、renderApp、renderApps、stream 和 dispose。接受包括 request 在内的批处理选项,并将其信号与 options.signal 组合。不要在模块全局作用域创建该对象。dispose(reason) 会中止、释放 listeners 并清空 locals。
import { createServerRequest, renderStateScript, escapeHtml, withSecurityHeaders } from 'wu-framework/server';
export const GET = withSecurityHeaders(async (request, { nonce }) => {
const scope = createServerRequest({ request, nonce, timeout: 2000, concurrency: 2 });
try {
scope.locals.set('requestId', crypto.randomUUID());
const rendered = await scope.renderApps([{
name: 'greeting',
load: async ({ signal }) => {
signal.throwIfAborted();
return { props: { label: 'Hello' }, publicState: { label: 'Hello' } };
},
module: { render: props => '<p>' + escapeHtml(props.label) + '</p>' },
}]);
return new Response(rendered.html + renderStateScript(rendered.state, { nonce }), {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
} finally {
scope.dispose();
}
});
load(context) 接收 { appName, signal, request, locals },返回 { props?, publicState? }。使用 loader 时,仅 publicState 成为水合状态;私有 props 和 renderer 返回的 state 都不会自动发布。不使用 loader 时保留 renderer 的 state。这无法阻止 renderer 自身将秘密写入 HTML。
SSR 取消、时限与并发
renderApp、renderApps 和 renderAppsStream 接受 signal 和包含 loader 在内的每应用 timeout;timeout: 0 关闭时限。批次和流接受 concurrency,默认无限制。中止会保留 signal.reason;未提供原因时使用 WU_SSR_ABORTED。超时产生 WU_SSR_TIMEOUT。Renderers 和 loaders 必须配合 context.signal 停止工作。
未启用 failFast 时,单个应用失败允许部分降级;使用 failFast: true 时中止批次。取消流会将中止传播给待处理工作。返回 HTTP 流时,应保留上下文直到消费完成或取消;不要在构造响应后立即调用 dispose()。
// Reusable wrappers: a function generates a new nonce for each request.
wuSecurityMiddleware({ nonce: () => createNonce() });
withSecurityHeaders(handler, { nonce: () => createNonce() });
// Low-level helpers still accept the nonce of this response.
const nonce = createNonce();
const headers = securityHeaders({ nonce });