声明式 Shadow DOM
我们很高兴地宣布,Safari Technology Preview 162 已添加并默认启用声明式 Shadow DOM API 支持。回顾一下,Shadow DOM 是 Web Components 的一部分,后者是由 Google 最初提出的一组规范,旨在实现在网络上创建可重用的小部件和组件。从那时起,这些规范已被整合到 DOM 和 HTML 标准中。特别是,Shadow DOM 为 DOM 树提供了轻量级封装,它允许在一个元素上创建一个名为“影子树”(shadow tree)的并行树,该树替换了元素的渲染,而无需修改其自身的 DOM 树。
在此之前,在元素上创建影子树需要在 JavaScript 中调用元素的 attachShadow()
方法。这意味着当 JavaScript 被禁用时(例如在电子邮件客户端中),此功能不可用,并且需要小心地隐藏本应在影子树中的内容,直到相关脚本加载完毕,以避免内容闪烁(flush of contents)。此外,许多现代网站和基于 Web 的应用程序采用一种称为“服务器端渲染”的技术,即在 Web 服务器上运行的程序生成带有初始内容的 HTML 标记供 Web 浏览器使用,而不是在脚本加载后通过网络获取内容。这有助于减少页面加载时间,并改善SEO,因为页面内容可以立即供搜索引擎爬虫使用。许多服务器端渲染技术试图在初始渲染时消除对 JavaScript 的需求,以减少初始绘制延迟(initial paint latency),并随着脚本和相关元数据加载而逐步增强内容的交互性。不幸的是,在使用 Shadow DOM 时,由于前面提到的需要使用 attachShadow()
的要求,这无法实现。
声明式 Shadow DOM 通过提供一种在 HTML 中包含 Shadow DOM 内容的机制来解决这些用例。具体来说,在 template
元素上指定 shadowrootmode
内容属性会告诉 Web 浏览器,此 template
元素内部的内容应放入附加到其父元素的影子树中。例如,在以下示例中,带有 shadowrootmode
的 template
元素将在 some-component
元素上附加一个影子根,该影子根包含一个文本节点“hello, world.”作为其唯一的子节点。
<some-component>
<template shadowrootmode="closed">hello, world.</template>
</some-component>
当脚本加载并准备好使此内容具有交互性时,可以通过 ElementInternals
访问影子根,如下所示
customElements.define('some-component', class SomeComponent extends HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
// This will log "hello, world."
console.log(this.#internals.shadowRoot.textContent.trim());
}
});
我们在设计此 API 时考虑了向后兼容性。例如,在具有声明式 Shadow DOM 的元素上调用 attachShadow()
会返回声明式附加的影子根,并删除其所有子节点,而不是通过抛出异常而失败。这意味着采用声明式 Shadow DOM 与依赖 attachShadow()
创建影子根的现有 JavaScript 兼容。请注意,默认情况下,任何 JavaScript 解析器 API(例如 DOMParser
和 innerHTML
)都不支持声明式 Shadow DOM,以避免在接受任意模板内容的现有网站中创建新的跨站脚本漏洞(因为此类内容中的 script
元素以前是惰性的,不会运行)。
此外,我们正在引入克隆影子根的能力。在此之前,ShadowRoot
及其后代节点无法通过 cloneNode()
或 importNode()
克隆。attachShadow()
现在接受一个 cloneable
标志作为选项。当此标志设置为 true 时,现有的 JavaScript API(例如 cloneNode()
和 importNode()
)在克隆其影子宿主时也会克隆 ShadowRoot
。声明式 Shadow DOM 会自动将此标志设置为 true,以便出现在其他 template
元素内部的声明式 Shadow DOM 可以与其宿主一起被克隆。在以下示例中,外部模板元素包含一个 some-component
元素实例,其影子树内容使用声明式 Shadow DOM 进行序列化。使用 document.importNode(template1.content, true)
克隆 template1.content
将克隆 some-component
及其(声明式定义的)影子树。
<template id="template1">
<some-component>
<template shadowrootmode="closed">hello, world.</template>
</some-component>
</template>
总而言之,声明式 Shadow DOM 引入了一种在 HTML 中定义影子树的令人兴奋的新方式,这对于 Web Components 的服务器端渲染以及在 JavaScript 被禁用的环境(例如电子邮件客户端)中都将非常有用。这是一个备受期待的功能,浏览器供应商之间进行了大量讨论。我们很高兴地报告它已在 Safari Technology Preview 162 中引入。