WU / 文档通用微前端
中文

渲染与 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>'); // '&lt;etiqueta&gt;'

不验证 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 });
一起完善 WU

发现了问题?有新的想法?

你的使用体验能让 WU 变得更好。告诉我们遇到的问题,或支持项目的持续开发。