你正在查看 Lit 的旧版本文档。点击 这里查看最新版本。

Lit SSR 服务端用法

This package is part of the Lit Labs family of experimental packages. See the Lit Labs page for guidance on using Labs software in production.

服务端渲染从使用 @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';

const result = render(html`
<h1>Hello SSR!</h1>
<my-element></my-element>
`);

要渲染单个元素,你可以渲染一个仅包含该元素的模板:

const result = render(html`<my-element></my-element>`);

render() 返回一个 RenderResult:一个可迭代的值集合,可以进行流式传输或拼接为字符串。

RenderResult 可以包含字符串、嵌套的渲染结果,或字符串/渲染结果的 Promise。并非所有渲染结果都包含 Promise——当自定义元素执行异步任务(如获取数据)时才会出现——但由于 RenderResult 可能包含 Promise,将其处理为字符串或 HTTP 响应可能是一个异步操作。

即使 RenderResult 可能包含 Promise,它仍然是一个同步可迭代对象,而不是异步可迭代对象。这是因为同步可迭代对象比异步可迭代对象更快,而且许多服务端渲染不需要异步渲染,因此不应承担异步可迭代对象的开销。

在同步可迭代对象中允许 Promise 创建了一种混合的同步/异步迭代协议。在消费 RenderResult 时,你必须检查每个值是否是 Promise 或可迭代对象,并根据需要等待或递归。

@lit-labs/ssr 包含三个工具来帮你完成这些操作:

  • RenderResultReadable
  • collectResult()
  • collectResultSync()

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(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(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 模块
优点:
  • 使用简单。可以直接导入组件模块并使用模板调用 render()
缺点:
  • 自定义元素注册在跨不同渲染请求共享的注册表中。
优点:
  • 跨不同渲染请求隔离上下文。
缺点:
  • 用法不太直观。需要编写并指定一个包含调用函数的模块文件。
  • 由于模块图需要在每个请求中重新求值,速度较慢。

使用全局作用域时,你只需使用模板调用 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);
});

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';

// ...

// 例如在 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 = Readable.from(ssrResult);
});

注意:使用此功能需要 Node 14+ 并向 Node 传递 --experimental-vm-modules 标志,因为它使用实验性的 VM 模块来创建兼容模块的 VM 上下文。