渲染上下文
RenderContext 是 Esmx 框架中的一个核心类,主要负责服务端渲染(SSR)过程中的资源管理和 HTML 生成。它具有以下核心特点:
-
基于 ESM 的模块系统
- 采用现代的 ECMAScript Modules 标准
- 支持原生的模块导入导出
- 实现了更好的代码分割和按需加载
-
智能依赖收集
- 基于实际渲染路径动态收集依赖
- 避免不必要的资源加载
- 支持异步组件和动态导入
-
精确的资源注入
- 严格控制资源加载顺序
- 优化首屏加载性能
- 确保客户端激活(Hydration)的可靠性
-
灵活的配置机制
- 支持动态基础路径配置
- 提供多种导入映射模式
- 适应不同的部署场景
使用方式
在 Esmx 框架中,开发者通常不需要直接创建 RenderContext 实例,而是通过 esmx.render()
方法来获取实例:
src/entry.node.ts
1async server(esmx) {
2 const server = http.createServer((req, res) => {
3 // 静态文件处理
4 esmx.middleware(req, res, async () => {
5 // 通过 esmx.render() 获取 RenderContext 实例
6 const rc = await esmx.render({
7 params: {
8 url: req.url
9 }
10 });
11 // 响应 HTML 内容
12 res.end(rc.html);
13 });
14 });
15}
主要功能
依赖收集
RenderContext 实现了一套智能的依赖收集机制,它基于实际渲染的组件来动态收集依赖,而不是简单地预加载所有可能用到的资源:
按需收集
- 在组件实际渲染过程中自动追踪和记录模块依赖
- 只收集当前页面渲染时真正使用到的 CSS、JavaScript 等资源
- 通过
importMetaSet
精确记录每个组件的模块依赖关系
- 支持异步组件和动态导入的依赖收集
自动化处理
- 开发者无需手动管理依赖收集过程
- 框架自动在组件渲染时收集依赖信息
- 通过
commit()
方法统一处理所有收集到的资源
- 自动处理循环依赖和重复依赖的问题
性能优化
- 避免加载未使用的模块,显著减少首屏加载时间
- 精确控制资源加载顺序,优化页面渲染性能
- 自动生成最优的导入映射(Import Map)
- 支持资源预加载和按需加载策略
资源注入
RenderContext 提供了多个方法来注入不同类型的资源,每个方法都经过精心设计以优化资源加载性能:
preload()
:预加载 CSS 和 JS 资源,支持优先级配置
css()
:注入首屏样式表,支持关键 CSS 提取
importmap()
:注入模块导入映射,支持动态路径解析
moduleEntry()
:注入客户端入口模块,支持多入口配置
modulePreload()
:预加载模块依赖,支持按需加载策略
资源注入顺序
RenderContext 严格控制资源注入顺序,这种顺序设计是基于浏览器的工作原理和性能优化考虑:
-
head 部分:
preload()
:预加载 CSS 和 JS 资源,让浏览器尽早发现并开始加载这些资源
css()
:注入首屏样式表,确保页面样式在内容渲染时就位
-
body 部分:
importmap()
:注入模块导入映射,定义 ESM 模块的路径解析规则
moduleEntry()
:注入客户端入口模块,必须在 importmap 之后执行
modulePreload()
:预加载模块依赖,必须在 importmap 之后执行
完整渲染流程
一个典型的 RenderContext 使用流程如下:
src/entry.server.ts
1export default async (rc: RenderContext) => {
2 // 1. 渲染页面内容并收集依赖
3 const app = createApp();
4 const html = await renderToString(app, {
5 importMetaSet: rc.importMetaSet
6 });
7
8 // 2. 提交依赖收集
9 await rc.commit();
10
11 // 3. 生成完整 HTML
12 rc.html = `
13 <!DOCTYPE html>
14 <html>
15 <head>
16 ${rc.preload()}
17 ${rc.css()}
18 </head>
19 <body>
20 ${html}
21 ${rc.importmap()}
22 ${rc.moduleEntry()}
23 ${rc.modulePreload()}
24 </body>
25 </html>
26 `;
27};
高级特性
基础路径配置
RenderContext 提供了一个灵活的动态基础路径配置机制,支持在运行时动态设置静态资源的基础路径:
src/entry.node.ts
1const rc = await esmx.render({
2 base: '/esmx', // 设置基础路径
3 params: {
4 url: req.url
5 }
6});
这种机制特别适用于以下场景:
-
多语言站点部署
主域名.com → 默认语言
主域名.com/cn/ → 中文站点
主域名.com/en/ → 英文站点
-
微前端应用
- 支持子应用在不同路径下灵活部署
- 便于集成到不同的主应用中
导入映射模式
RenderContext 提供了两种导入映射(Import Map)模式:
-
Inline 模式(默认)
- 将导入映射直接内联到 HTML 中
- 适合小型应用,减少额外的网络请求
- 页面加载时立即可用
-
JS 模式
- 通过外部 JavaScript 文件加载导入映射
- 适合大型应用,可以利用浏览器缓存机制
- 支持动态更新映射内容
可以通过配置选择合适的模式:
src/entry.node.ts
1const rc = await esmx.render({
2 importmapMode: 'js', // 'inline' | 'js'
3 params: {
4 url: req.url
5 }
6});
入口函数配置
RenderContext 支持通过 entryName
配置来指定服务端渲染的入口函数:
src/entry.node.ts
1const rc = await esmx.render({
2 entryName: 'mobile', // 指定使用移动端入口函数
3 params: {
4 url: req.url
5 }
6});
这种机制特别适用于以下场景:
-
多模板渲染
src/entry.server.ts
1// 移动端入口函数
2export const mobile = async (rc: RenderContext) => {
3 // 移动端特定的渲染逻辑
4};
5
6// 桌面端入口函数
7export const desktop = async (rc: RenderContext) => {
8 // 桌面端特定的渲染逻辑
9};
-
A/B 测试
- 支持同一页面使用不同的渲染逻辑
- 便于进行用户体验实验
- 灵活切换不同的渲染策略
-
特殊渲染需求
- 支持某些页面使用自定义的渲染流程
- 适应不同场景的性能优化需求
- 实现更精细的渲染控制
最佳实践
-
获取 RenderContext 实例
- 始终通过
esmx.render()
方法获取实例
- 根据需要传入适当的参数
- 避免手动创建实例
-
依赖收集
- 确保所有模块都正确调用
importMetaSet.add(import.meta)
- 在渲染完成后立即调用
commit()
方法
- 合理使用异步组件和动态导入优化首屏加载
-
资源注入
- 严格遵循资源注入顺序
- 不要在 body 中注入 CSS
- 确保 importmap 在 moduleEntry 之前
-
性能优化
- 使用 preload 预加载关键资源
- 合理使用 modulePreload 优化模块加载
- 避免不必要的资源加载
- 利用浏览器缓存机制优化加载性能