为 Lit SSR 编写组件
Lit 在服务器环境中渲染 Web 组件的方法对组件代码施加了一些限制,以实现高效的服务器渲染。在编写组件时,请记住这些注意事项,以确保它们与 Lit SSR 兼容。
注意:本页列出的限制可能会随着我们对 Lit SSR 的改进而变化。如果你希望看到支持某个特定用例,请提交问题或开始讨论。
仅限浏览器的代码
Permalink to "仅限浏览器的代码"大多数浏览器 DOM API 在 Node 环境中不可用。Lit SSR 使用的 DOM 模拟是渲染 Lit 模板和组件所需的最低限度。有关可用 API 的完整列表,请参阅 DOM 模拟页面。
在编写组件时,请从仅在客户端调用的生命周期方法中执行必要的 DOM 操作,而不是在服务器上。例如,如果你需要测量更新后的 DOM,请使用 updated()
。这个回调仅在浏览器上运行,因此可以安全地访问 DOM。
请参阅下面的生命周期部分,了解哪些特定方法在服务器上调用,哪些仅限于浏览器。
定义 Lit 组件的某些模块可能还有使用浏览器 API 的副作用——例如检测某些浏览器功能——以至于该模块在非浏览器环境中导入时会出错。在这种情况下,你可以将副作用代码移到仅限浏览器的生命周期回调中,或者添加条件使其仅在浏览器中运行。
对于简单的情况,在某些 DOM 访问中添加条件或可选链可能足以防止不可用的 DOM API。例如:
const hasConstructableStylesheets = typeof globalThis.CSSStyleSheet?.prototype.replaceSync === 'function';
lit
包还提供了一个 isServer
环境检查器,可用于编写针对不同环境的条件代码块:
import {isServer} from 'lit';
if (isServer) {
// 仅在服务器环境(如 Node)中运行
} else {
// 在浏览器中运行
}
对于更复杂的用例,考虑利用条件导出,特别是匹配 "node"
环境的条件,这样你可以根据模块是在 Node 还是浏览器中导入来提供不同的代码。用户会根据是从 Node 还是浏览器导入包获得适当的版本。导出条件也受到流行的打包工具的支持,如 rollup 和 webpack,因此用户可以为你的包引入适当的代码。
一个示例配置可能如下所示:
// package.json
{
"name": "my-awesome-lit-components",
"exports": {
"./button.js": {
"node": "./button-node.js",
"default": "./button.js"
}
}
}
Node 入口点文件可以手动创建,或者你可以使用打包器来生成它们。
如果可能,避免将 Lit 内联打包到发布的组件中。
因为 Lit 包使用条件导出为 Node 和浏览器环境提供不同的模块,我们强烈建议不要将 lit
打包到发布到 NPM 的包中。如果你这样做,你的包将只包含为你打包时的环境准备的 lit
模块,并且不会根据环境自动切换。
如果你使用像 ESBuild 或 Rollup 这样的打包器来转换你的代码,你可以将包标记为 external,这样它们就不会被打包到你的组件中。ESBuild 有一个 packages
选项来外部化所有依赖项,或者你可以在 external 选项中只标记 lit
和相关包。类似地,Rollup 也有一个等效的 "external" 配置选项。
如果你必须将 Lit 库代码打包到你的组件中(例如,通过 CDN 分发),我们建议创建两个入口点:一个用于浏览器,一个用于 Node。打包器将有选项来选择目标平台(如浏览器或 Node),或者允许你明确指定用于解析模块的导出条件。
例如,ESBuild 有 platform
选项,在 Rollup 中,你可以为 @rollup/plugin-node-resolve
的 exportConditions
选项提供 "node"
。
必须在组件库的 package.json
文件中指定这些浏览器和 Node 目标的入口点。有关更多详细信息,请参阅条件导出。
在服务器端渲染期间,只运行某些生命周期回调。这些回调为组件生成初始样式和标记。额外的生命周期方法在客户端水合期间和组件水合后的运行时内调用。
下表列出了标准自定义元素和 Lit 生命周期方法,以及它们是否在 SSR 期间被调用。所有生命周期在元素注册和水合后的浏览器上都是可用的。
在服务器上调用的方法不应包含对尚未模拟的浏览器/DOM API 的引用。服务器端不调用的方法可以包含这些引用,而不会导致破坏。
在 Lit SSR 作为 Lit Labs 的一部分时,方法是否在服务器上调用可能会发生变化。
标准自定义元素和 LitElement
Permalink to "标准自定义元素和 LitElement"方法 | 在服务器上调用 | 注释 |
---|---|---|
constructor() | 是 ⚠️ | |
connectedCallback() | 否 | |
disconnectedCallback() | 否 | |
attributeChangedCallback() | 否 | |
adoptedCallback() | 否 | |
hasChanged() | 是 ⚠️ | 当属性被设置时调用 |
shouldUpdate() | 否 | |
willUpdate() | 是 ⚠️ | 在 render() 之前调用 |
update() | 否 | |
render() | 是 ⚠️ | |
firstUpdate() | 否 | |
updated() | 否 |
ReactiveController
Permalink to "ReactiveController"方法 | 在服务器上调用 | 注释 |
---|---|---|
constructor() | 是 ⚠️ | |
hostConnected() | 否 | |
hostDisconnected() | 否 | |
hostUpdate() | 否 | |
hostUpdated() | 否 |
Directive
Permalink to "Directive"方法 | 在服务器上调用 | 注释 |
---|---|---|
constructor() | 是 ⚠️ | |
update() | 否 | |
render() | 是 ⚠️ | |
disconnected() | 否 | 仅异步指令 |
reconnected() | 否 | 仅异步指令 |
目前没有机制可以在继续渲染之前等待异步结果(例如来自异步指令或控制器的结果),尽管我们正在考虑未来允许这种情况的选项。目前的解决方法是在服务器上渲染顶级模板之前执行任何异步工作,并将数据作为某种属性或特性提供给模板。
例如:
- 异步指令如
asyncAppend()
或asyncReplace()
将不会在服务器端生成任何可渲染的结果。 until()
指令只会产生优先级最高的非 promise 占位符值。
@lit-labs/testing
包包含使用 Web Test Runner 插件创建测试夹具的工具函数,这些夹具使用 @lit-labs/ssr
在服务器端渲染。它可以帮助测试你的组件是否可以在服务器端渲染。在 readme 中查看更多信息。