Lit SSR 服务端用法
服务端渲染从使用 @lit-labs/ssr
包中提供的特定于服务器的 render()
函数来渲染 Lit 模板 开始。
render 函数的签名是:
render(value: unknown, renderInfo?: Partial<RenderInfo>): RenderResult
通常,value
是由 Lit 模板表达式产生的 TemplateResult
,如:
html`<h1>Hello</h1>`
模板可以包含自定义元素。如果自定义元素在服务器上已定义,它们将依次被渲染,连同它们的模板一起。
import {render} from '@lit-labs/ssr';
import {html} from 'lit';
// 在服务器上导入 `my-element` 以便服务端渲染它。
import './my-element.js';
const result = render(html`
<h1>Hello SSR!</h1>
<my-element></my-element>
`);
要渲染单个元素,你可以渲染一个只包含该元素的模板:
import {html} from 'lit';
import './my-element.js';
const result = render(html`<my-element></my-element>`);
处理 RenderResults
Permalink to "处理 RenderResults"render()
返回一个 RenderResult
:一个可以流式传输或连接成字符串的值的可迭代对象。
RenderResult
可以包含字符串、嵌套的渲染结果,或字符串或渲染结果的 Promise。并非所有渲染结果都包含 Promise——这些可能发生在自定义元素执行异步任务时,如获取数据——但由于 RenderResult
可以包含 Promise,将其处理成字符串或 HTTP 响应 潜在地 是一个异步操作。
即使 RenderResult
可以包含 Promise,它仍然是一个同步可迭代对象,而不是异步可迭代对象。这是因为同步可迭代对象比异步可迭代对象更快,并且许多服务器渲染不需要异步渲染,因此不应该为异步可迭代对象支付开销。
在同步可迭代对象中允许 Promise 会创建一种混合同步/异步迭代协议。在消费 RenderResult
时,你必须检查每个值以查看它是否是 Promise 或可迭代对象,并根据需要等待或递归。
@lit-labs/ssr
包含三个工具来为你处理这个问题:
RenderResultReadable
collectResult()
collectResultSync()
RenderResultReadable
Permalink to "RenderResultReadable" RenderResultReadable
是一个 Node Readable
流实现,它提供来自 RenderResult
的值。这可以被管道传输到 Writable
流,或传递给像 Koa 这样的 Web 服务器框架。
当与流式 HTTP 服务器或其他支持流的 API 集成时,这是处理 SSR 结果的首选方式。
import {render} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {html} from 'lit';
// 使用 Koa 进行流式传输
app.use(async (ctx) => {
const result = render(html`<my-element></my-element>`);
ctx.type = 'text/html';
ctx.body = new RenderResultReadable(result);
});
collectResult()
Permalink to "collectResult()" collectResult(result: RenderResult): Promise<string>
collectResult()
是一个异步函数,它接收一个 RenderResult
并将其连接成一个字符串。它等待 Promise 并递归到嵌套的可迭代对象中。
示例
import {render} from '@lit-labs/ssr';
import {collectResult} from '@lit-labs/ssr/lib/render-result.js';
import {html} from 'lit';
const result = render(html`<my-element></my-element>`);
const contents = await collectResult(result);
collectResultSync()
Permalink to "collectResultSync()" collectResultSync(result: RenderResult): Promise<string>
collectResultSync()
是一个同步函数,它接收一个 RenderResult
并将其连接成一个字符串。它递归到嵌套的可迭代对象中,但在遇到 Promise 时 抛出异常。
由于此函数不支持异步渲染,建议仅在无法等待异步函数时使用它。
import {render} from '@lit-labs/ssr';
import {collectResultSync} from '@lit-labs/ssr/lib/render-result.js';
import {html} from 'lit';
const result = render(html`<my-element></my-element>`);
// 如果 `result` 包含 Promise,则抛出异常!
const contents = collectResultSync(result);
render()
的第二个参数是一个 RenderInfo
对象,用于将选项和当前渲染状态传递给组件和子模板。
调用者可以设置的主要选项有:
deferHydration
:控制顶级自定义元素是否添加defer-hydration
属性,以指示元素不应自动水合。默认为false
,因此顶级元素 确实 自动水合。elementRenderers
:用于渲染自定义元素的ElementRenderer
类数组。默认情况下,这包含LitElementRenderer
来渲染 Lit 元素。可以设置为包含自定义ElementRenderer
实例(文档即将推出),或设置为空数组以完全禁用自定义元素渲染。
在 VM 模块或全局作用域中运行 SSR
Permalink to "在 VM 模块或全局作用域中运行 SSR"为了在 Node 中渲染自定义元素,必须首先使用全局 customElements
API 定义和注册它们,这是一个仅浏览器特性。因此,当 Lit 在 Node 中运行时,它会自动使用在服务器上渲染 Lit 所需的最小 DOM API 集,并定义 customElements
全局变量。(有关模拟 API 的列表,请参阅 DOM 模拟。)
Lit SSR 提供了两种在服务器端渲染自定义元素的不同方式:在全局作用域中渲染或通过 VM 模块渲染。VM 模块利用 Node 的 vm.Module
API,能够在 V8 虚拟机上下文中运行代码。这两种方法主要在如何共享全局状态(例如自定义元素注册表)方面有所不同。
在全局作用域中渲染时,将定义一个单一的共享 customElements
注册表,并在所有渲染请求之间共享,以及组件代码可能设置的任何其他全局状态。
使用 VM 模块渲染允许每个渲染请求都有自己的上下文,该上下文与主 Node 进程有不同的全局。customElements
注册表将仅安装在该上下文中,其他全局状态也将隔离到该上下文。VM 模块是一个实验性的 Node 功能。
全局 | VM 模块 |
---|---|
优点:
| 优点:
|
全局作用域
Permalink to "全局作用域"使用全局作用域时,你可以直接使用模板调用 render()
来获取 RenderResult
并将其传递给你的服务器:
import {render} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {myTemplate} from './my-template.js';
// ...
// 例如,在 Koa 中间件中
app.use(async (ctx) => {
const ssrResult = render(myTemplate(data));
ctx.type = 'text/html';
ctx.body = new RenderResultReadable(ssrResult);
});
VM 模块
Permalink to "VM 模块"Lit 还提供了一种方法,可以将应用代码加载到单独的 VM 上下文中并从中渲染,该上下文具有自己的全局对象。
// render-template.js
import {render} from '@lit-labs/ssr';
import {myTemplate} from './my-template.js';
export const renderTemplate = (someData) => {
return render(myTemplate(someData));
};
// server.js
import {ModuleLoader} from '@lit-labs/ssr/lib/module-loader.js';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
// ...
// 例如,在 Koa 中间件中
app.use(async (ctx) => {
const moduleLoader = new ModuleLoader();
const importResult = await moduleLoader.importModule(
'./render-template.js', // 在 VM 上下文中加载的模块
import.meta.url // 模块的引用 URL
);
const {renderTemplate} = importResult.module.namespace
as typeof import('./render-template.js')
const ssrResult = await renderTemplate({some: "data"});
ctx.type = 'text/html';
ctx.body = new RenderResultReadable(ssrResult);
});
// server.js
import {ModuleLoader} from '@lit-labs/ssr/lib/module-loader.js';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
// ...
// 例如,在 Koa 中间件中
app.use(async (ctx) => {
const moduleLoader = new ModuleLoader();
const importResult = await moduleLoader.importModule(
'./render-template.js', // 在 VM 上下文中加载的模块
import.meta.url // 模块的引用 URL
);
const {renderTemplate} = importResult.module.namespace;
const ssrResult = await renderTemplate({some: "data"});
ctx.type = 'text/html';
ctx.body = new RenderResultReadable(ssrResult);
});
注意:使用此功能需要 Node 14+ 并向 Node 传递 --experimental-vm-modules
标志,因为它使用实验性 VM 模块来创建模块兼容的 VM 上下文。