Lit¶
背景¶
Lit 是一个用于创建 Web 组件的小型库,由 Google 维护。它旨在通过消除样板代码并提供更声明式的语法和重新渲染优化来改善 Web 组件的创作体验,对于有使用流行的基于组件的前端框架经验的开发人员来说应该会感到熟悉。
Mozilla 开发人员于 2021 年开始尝试使用 Lit 构建少量新的 Web 组件。开发人员体验和生产力优势非常明显,因此负责构建新的 可复用组件 库的团队在 2022 年底将 Lit 引入 mozilla-central
以使其可用。现在可以在代码库中的任何位置使用 Lit 创建新的 Web 组件。
使用 Lit¶
Lit 在其网站上提供了全面的文档,在构建基于 Lit 的自定义元素时应与本文档一起参考: https://lit.npmjs.net.cn/docs/
虽然 Lit 最初是为了帮助可复用组件团队的工作而引入的,但它也可以用于在整个 mozilla-central
中创建可复用和特定于领域的 UI 组件。迄今为止,使用 Lit 创建的自定义元素的一些示例包括 moz-toggle、moz-button-group 和凭据管理团队的 login-timeline 组件。
何时使用 Lit¶
如果您正在构建需要高效响应状态更改的高度反应式元素,则 Lit 可能是特别好的选择。Lit 的声明式 模板 和 反应式属性 可以处理很多确定 UI 的哪些部分应该响应特定更改而更新的工作。
因为 Lit 组件最终只是 Web 组件,所以您可能还想仅仅因为它提供的一些语法而使用它,例如允许您在 JavaScript 代码旁边编写模板代码、在模板中绑定事件监听器和属性,以及自动创建一个开放的 shadowRoot
。
何时不使用 Lit¶
在您想要 扩展内置元素 的情况下,无法使用 Lit。Lit 只能用于创建 自主自定义元素,即扩展 HTMLElement
的元素。
使用 Lit 编写组件¶
除了装饰器之外,Lit 库的所有标准功能都可以在 mozilla-central
中使用,但在将 Lit 用于 Firefox 代码时,您应该注意一些特殊考虑因素和特定文件。
使用外部样式表¶
尽管 Lit 文档 明确建议不要 使用这种方法,但在 mozilla-central
中,使用外部样式表是为基于 Lit 的组件设置样式的首选方法。他们列出的注意事项与我们的用例不相关,我们已实施平台级解决方法以确保外部样式不会导致未设置样式的内容闪烁。使用外部样式表可以使我们的自动 lint 和审查工具检测到 CSS 更改,并有助于提高 Mozilla 的 desktop-theme-reviewers
小组的可见性。
lit.all.mjs
供应商文件¶
可以在 toolkit/content/widgets/vendor/lit.all.mjs 中找到一个经过稍微定制的 Lit 供应商版本。 mozilla-central
中的 Lit 版本应用了许多补丁以禁用缩小、源映射和某些警告消息,以及替换 innerHTML
使用 DOMParser
的补丁以及稍微修改 styleMap
指令的行为。有关这些补丁的更多详细信息,以及有关如何更新 lit.all.mjs
的信息,可以 在这里 找到。
因为我们提供的 Lit 版本将一些不同的 Lit 源文件的内容捆绑到一个文件中,所以通常来自不同文件的导入将直接从 lit.all.mjs
中提取。例如,使用 Lit npm 包时,如下所示的导入
// Standard npm package.
import { LitElement } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
在 mozilla-central
中将如下所示
// All imports come from a single file (relative path also works).
import { LitElement, classMap, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
MozLitElement
和 lit-utils.mjs
¶
MozLitElement 是 LitElement
类的一个扩展,它添加了一些功能使其更适合 Mozilla 开发人员的需求。在几乎所有情况下,都应使用 MozLitElement
作为新基于 Lit 的自定义元素的基础类,而不是 LitElement
。
它可以从 lit-utils.js
中导入并按如下方式使用
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
class MyCustomElement extends MozLitElement {
...
}
MozLitElement
在几个重要方面与 LitElement
不同
它为 shadow DOM 提供自动的 Fluent 支持¶
在 shadow DOM 中使用 Fluent 时,必须先连接元素的 shadowRoot
才能使用 Fluent。MozLitElement
通过扩展 LitElement
的 connectedCallback
来处理此问题,以 调用 document.l10n.connectRoot
(如果需要)。MozLitElement
还在每次元素更新时自动在 renderRoot 上调用 document.l10n.translateFragment
。这些修改的最终结果是,您可以在基于 Lit 的组件中像在 mozilla-central
中的其他任何标记中一样使用 Fluent。
它为本地化的反应式属性提供自动的 Fluent 支持¶
Fluent 要求如果属性不属于 允许的属性 的默认列表,则必须将其标记为安全。通过在反应式属性的定义中设置 fluent: true
,MozLitElement
将自动填充 connectedCallback()
中的 data-l10n-attrs
以将属性标记为 Fluent 安全。
class MyCustomElement extends MozLitElement {
static properties = {
label: { type: String, fluent: true },
description: { type: String, fluent: true },
value: { type: String },
};
}
它为标准 Web 属性提供映射的属性助手¶
当您想在组件级别接受 accesskey、title 或 aria-label 等标准属性,但它实际上应该设置在子元素上时,您可以在属性定义中设置 mapped: true
选项,并且在设置属性时它将从主机中删除。请注意,一旦设置了属性,就无法取消设置。
class MyElement extends MozLitElement {
static properties = {
accessKey: { type: String, mapped: true },
};
render() {
return html`<button accesskey=${this.accessKey}>Hello</button>`;
}
}
它实现了对 Lit 的 @query
和 @queryAll
装饰器的支持¶
Lit 库包含 @query
和 @queryAll
装饰器,它们提供了一种简单的方法来查找内部组件 DOM 中的元素。这些在 mozilla-central
中不起作用,因为我们不支持 JavaScript 装饰器。相反,MozLitElement
通过在子类上定义静态 queries
属性提供了等效的 DOM 查询功能。例如,以下查询组件的 DOM 以查找某些选择器并将结果分配给不同类属性的 Lit 代码
import { LitElement, html } from "lit";
import { query } from "lit/decorators/query.js";
class MyCustomElement extends LitElement {
@query("#title");
_title;
@queryAll("p");
_paragraphs;
render() {
return html`
<p id="title">The title</p>
<p>Some other paragraph.</p>
`;
}
}
在 mozilla-central
中等效于以下代码
import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
class MyCustomElement extends MozLitElement {
static queries = {
_title: "#title", // equivalent to @query
_paragraphs: { all: "p" }, // equivalent to @queryAll
};
render() {
return html`
<p id="title">The title</p>
<p>Some other paragraph.</p>
`;
}
}
它添加了一个 dispatchOnUpdateComplete
方法¶
dispatchOnUpdateComplete
方法提供了一种简单的方法来告知测试代码或其他元素使用者反应式属性更改已生效。它利用 Lit 的 updateComplete promise 在应用所有更新并且组件的 DOM 准备好进行查询后发出事件。例如,当您需要在测试代码中查询 DOM 时,它可能特别有用
// my-custom-element.mjs
class MyCustomElement extends MozLitElement {
static properties = {
clicked: { type: Boolean },
};
async handleClick() {
if (!this.clicked) {
this.clicked = true;
}
this.dispatchOnUpdateComplete(new CustomEvent("button-clicked"));
}
render() {
return html`
<p>The button was ${this.clicked ? "clicked" : "not clicked"}</p>
<button @click=${this.handleClick}>Click me!</button>
`;
}
}
// test_my_custom_element.mjs
add_task(async function testButtonClicked() {
let { button, message } = this.convenientHelperToGetElements();
is(message.textContent.trim(), "The button was not clicked");
let clicked = BrowserTestUtils.waitForEvent(button, "button-clicked");
synthesizeMouseAtCenter(button, {});
await clicked;
is(message.textContent.trim(), "The button was clicked");
});