RenderContext
RenderContext 是 Esmx 框架中的核心类,负责管理服务端渲染(SSR)的完整生命周期。它提供了一套完整的 API 来处理渲染上下文、资源管理、状态同步等关键任务:
- 渲染控制:管理服务端渲染流程,支持多入口渲染、条件渲染等场景
- 资源管理:智能收集和注入 JS、CSS 等静态资源,优化加载性能
- 状态同步:处理服务端状态序列化,确保客户端正确激活(hydration)
- 路由控制:支持服务端重定向、状态码设置等高级功能
类型定义
ServerRenderHandle
服务端渲染处理函数的类型定义。
1type ServerRenderHandle = (rc: RenderContext) => Promise<void> | void;
服务端渲染处理函数是一个异步或同步函数,接收 RenderContext 实例作为参数,用于处理服务端渲染逻辑。
entry.node.ts
1// 1. 异步处理函数
2export default async (rc: RenderContext) => {
3 const app = createApp();
4 const html = await renderToString(app);
5 rc.html = html;
6};
7
8// 2. 同步处理函数
9export const simple = (rc: RenderContext) => {
10 rc.html = '<h1>Hello World</h1>';
11};
RenderFiles
渲染过程中收集的资源文件列表的类型定义。
1interface RenderFiles {
2 js: string[];
3 css: string[];
4 modulepreload: string[];
5 resources: string[];
6}
- js: JavaScript 文件列表
- css: 样式表文件列表
- modulepreload: 需要预加载的 ESM 模块列表
- resources: 其他资源文件列表(图片、字体等)
1// 资源文件列表示例
2rc.files = {
3 js: [
4 '/assets/entry-client.js',
5 '/assets/vendor.js'
6 ],
7 css: [
8 '/assets/main.css',
9 '/assets/vendor.css'
10 ],
11 modulepreload: [
12 '/assets/Home.js',
13 '/assets/About.js'
14 ],
15 resources: [
16 '/assets/logo.png',
17 '/assets/font.woff2'
18 ]
19};
ImportmapMode
定义 importmap 的生成模式。
1type ImportmapMode = 'inline' | 'js';
inline
: 将 importmap 内容直接内联到 HTML 中,适用于以下场景:
- 需要减少 HTTP 请求数量
- importmap 内容较小
- 对首屏加载性能要求较高
js
: 将 importmap 内容生成为独立的 JS 文件,适用于以下场景:
- importmap 内容较大
- 需要利用浏览器缓存机制
- 多个页面共享相同的 importmap
渲染上下文类,负责服务端渲染(SSR)过程中的资源管理和 HTML 生成。
实例选项
定义渲染上下文的配置选项。
1interface RenderContextOptions {
2 base?: string
3 entryName?: string
4 params?: Record<string, any>
5 importmapMode?: ImportmapMode
6}
base
静态资源的基础路径。
- 所有静态资源(JS、CSS、图片等)都会基于此路径加载
- 支持运行时动态配置,无需重新构建
- 常用于多语言站点、微前端应用等场景
entryName
- 类型:
string
- 默认值:
'default'
服务端渲染入口函数名称。用于指定服务端渲染时使用的入口函数,当一个模块导出多个渲染函数时使用。
src/entry.server.ts
1export const mobile = async (rc: RenderContext) => {
2 // 移动端渲染逻辑
3};
4
5export const desktop = async (rc: RenderContext) => {
6 // 桌面端渲染逻辑
7};
params
- 类型:
Record<string, any>
- 默认值:
{}
渲染参数。可以传递任意类型的参数给渲染函数,常用于传递请求信息(URL、query 参数等)。
1const rc = await esmx.render({
2 params: {
3 url: req.url,
4 lang: 'zh-CN',
5 theme: 'dark'
6 }
7});
importmapMode
- 类型:
'inline' | 'js'
- 默认值:
'inline'
导入映射(Import Map)的生成模式:
实例属性
esmx
Esmx 实例引用。用于访问框架核心功能和配置信息。
redirect
- 类型:
string | null
- 默认值:
null
重定向地址。设置后,服务端可以根据此值进行 HTTP 重定向,常用于登录验证、权限控制等场景。
entry.node.ts
1// 登录验证示例
2export default async (rc: RenderContext) => {
3 if (!isLoggedIn()) {
4 rc.redirect = '/login';
5 rc.status = 302;
6 return;
7 }
8 // 继续渲染页面...
9};
10
11// 权限控制示例
12export default async (rc: RenderContext) => {
13 if (!hasPermission()) {
14 rc.redirect = '/403';
15 rc.status = 403;
16 return;
17 }
18 // 继续渲染页面...
19};
status
- 类型:
number | null
- 默认值:
null
HTTP 响应状态码。可以设置任意有效的 HTTP 状态码,常用于错误处理、重定向等场景。
entry.node.ts
1// 404 错误处理示例
2export default async (rc: RenderContext) => {
3 const page = await findPage(rc.params.url);
4 if (!page) {
5 rc.status = 404;
6 // 渲染 404 页面...
7 return;
8 }
9 // 继续渲染页面...
10};
11
12// 临时重定向示例
13export default async (rc: RenderContext) => {
14 if (needMaintenance()) {
15 rc.redirect = '/maintenance';
16 rc.status = 307; // 临时重定向,保持请求方法不变
17 return;
18 }
19 // 继续渲染页面...
20};
html
HTML 内容。用于设置和获取最终生成的 HTML 内容,在设置时自动处理基础路径占位符。
entry.node.ts
1// 基础用法
2export default async (rc: RenderContext) => {
3 // 设置 HTML 内容
4 rc.html = `
5 <!DOCTYPE html>
6 <html>
7 <head>
8 ${rc.preload()}
9 ${rc.css()}
10 </head>
11 <body>
12 <div id="app">Hello World</div>
13 ${rc.importmap()}
14 ${rc.moduleEntry()}
15 ${rc.modulePreload()}
16 </body>
17 </html>
18 `;
19};
20
21// 动态基础路径
22const rc = await esmx.render({
23 base: '/app', // 设置基础路径
24 params: { url: req.url }
25});
26
27// HTML 中的占位符会被自动替换:
28// [[[___GEZ_DYNAMIC_BASE___]]]/your-app-name/css/style.css
29// 替换为:
30// /app/your-app-name/css/style.css
base
- 类型:
string
- 只读:
true
- 默认值:
''
静态资源的基础路径。所有静态资源(JS、CSS、图片等)都会基于此路径加载,支持运行时动态配置。
1// 基础用法
2const rc = await esmx.render({
3 base: '/esmx', // 设置基础路径
4 params: { url: req.url }
5});
6
7// 多语言站点示例
8const rc = await esmx.render({
9 base: '/cn', // 中文站点
10 params: { lang: 'zh-CN' }
11});
12
13// 微前端应用示例
14const rc = await esmx.render({
15 base: '/app1', // 子应用1
16 params: { appId: 1 }
17});
entryName
- 类型:
string
- 只读:
true
- 默认值:
'default'
服务端渲染入口函数名称。用于从 entry.server.ts 中选择要使用的渲染函数。
entry.node.ts
1// 默认入口函数
2export default async (rc: RenderContext) => {
3 // 默认渲染逻辑
4};
5
6// 多个入口函数
7export const mobile = async (rc: RenderContext) => {
8 // 移动端渲染逻辑
9};
10
11export const desktop = async (rc: RenderContext) => {
12 // 桌面端渲染逻辑
13};
14
15// 根据设备类型选择入口函数
16const rc = await esmx.render({
17 entryName: isMobile ? 'mobile' : 'desktop',
18 params: { url: req.url }
19});
params
- 类型:
Record<string, any>
- 只读:
true
- 默认值:
{}
渲染参数。可以在服务端渲染过程中传递和访问参数,常用于传递请求信息、页面配置等。
1// 基础用法 - 传递 URL 和语言设置
2const rc = await esmx.render({
3 params: {
4 url: req.url,
5 lang: 'zh-CN'
6 }
7});
8
9// 页面配置 - 设置主题和布局
10const rc = await esmx.render({
11 params: {
12 theme: 'dark',
13 layout: 'sidebar'
14 }
15});
16
17// 环境配置 - 注入 API 地址
18const rc = await esmx.render({
19 params: {
20 apiBaseUrl: process.env.API_BASE_URL,
21 version: '1.0.0'
22 }
23});
importMetaSet
模块依赖收集集合。在组件渲染过程中自动追踪和记录模块依赖,只收集当前页面渲染时真正使用到的资源。
1// 基础用法
2const renderToString = (app: any, context: { importMetaSet: Set<ImportMeta> }) => {
3 // 在渲染过程中自动收集模块依赖
4 // 框架会在组件渲染时自动调用 context.importMetaSet.add(import.meta)
5 // 开发者无需手动处理依赖收集
6 return '<div id="app">Hello World</div>';
7};
8
9// 使用示例
10const app = createApp();
11const html = await renderToString(app, {
12 importMetaSet: rc.importMetaSet
13});
files
资源文件列表:
- js: JavaScript 文件列表
- css: 样式表文件列表
- modulepreload: 需要预加载的 ESM 模块列表
- resources: 其他资源文件列表(图片、字体等)
1// 资源收集
2await rc.commit();
3
4// 资源注入
5rc.html = `
6 <!DOCTYPE html>
7 <html>
8 <head>
9 <!-- 预加载资源 -->
10 ${rc.preload()}
11 <!-- 注入样式表 -->
12 ${rc.css()}
13 </head>
14 <body>
15 ${html}
16 ${rc.importmap()}
17 ${rc.moduleEntry()}
18 ${rc.modulePreload()}
19 </body>
20 </html>
21`;
importmapMode
- 类型:
'inline' | 'js'
- 默认值:
'inline'
导入映射的生成模式:
实例方法
serialize()
- 参数:
input: any
- 需要序列化的数据
options?: serialize.SerializeJSOptions
- 序列化选项
- 返回值:
string
将 JavaScript 对象序列化为字符串。用于在服务端渲染过程中序列化状态数据,确保数据可以安全地嵌入到 HTML 中。
1const state = {
2 user: { id: 1, name: 'Alice' },
3 timestamp: new Date()
4};
5
6rc.html = `
7 <script>
8 window.__INITIAL_STATE__ = ${rc.serialize(state)};
9 </script>
10`;
state()
- 参数:
varName: string
- 变量名
data: Record<string, any>
- 状态数据
- 返回值:
string
将状态数据序列化并注入到 HTML 中。使用安全的序列化方法处理数据,支持复杂的数据结构。
1const userInfo = {
2 id: 1,
3 name: 'John',
4 roles: ['admin']
5};
6
7rc.html = `
8 <head>
9 ${rc.state('__USER__', userInfo)}
10 </head>
11`;
commit()
提交依赖收集并更新资源列表。从 importMetaSet 中收集所有使用到的模块,基于 manifest 文件解析每个模块的具体资源。
1// 渲染并提交依赖
2const html = await renderToString(app, {
3 importMetaSet: rc.importMetaSet
4});
5
6// 提交依赖收集
7await rc.commit();
preload()
生成资源预加载标签。用于预加载 CSS 和 JavaScript 资源,支持优先级配置,自动处理基础路径。
1rc.html = `
2 <!DOCTYPE html>
3 <html>
4 <head>
5 ${rc.preload()}
6 ${rc.css()} <!-- 注入样式表 -->
7 </head>
8 <body>
9 ${html}
10 ${rc.importmap()}
11 ${rc.moduleEntry()}
12 ${rc.modulePreload()}
13 </body>
14 </html>
15`;
css()
生成 CSS 样式表标签。注入收集到的 CSS 文件,确保样式表按正确顺序加载。
1rc.html = `
2 <head>
3 ${rc.css()} <!-- 注入所有收集到的样式表 -->
4 </head>
5`;
importmap()
生成导入映射标签。根据 importmapMode 配置生成内联或外部导入映射。
1rc.html = `
2 <head>
3 ${rc.importmap()} <!-- 注入导入映射 -->
4 </head>
5`;
moduleEntry()
生成客户端入口模块标签。注入客户端入口模块,必须在 importmap 之后执行。
1rc.html = `
2 <body>
3 ${html}
4 ${rc.importmap()}
5 ${rc.moduleEntry()} <!-- 注入客户端入口模块 -->
6 </body>
7`;
modulePreload()
生成模块预加载标签。预加载收集到的 ESM 模块,优化首屏加载性能。
1rc.html = `
2 <body>
3 ${html}
4 ${rc.importmap()}
5 ${rc.moduleEntry()}
6 ${rc.modulePreload()} <!-- 预加载模块依赖 -->
7 </body>
8`;