包阅导读总结
1. 关键词:声明式 Shadow DOM、服务器端渲染、样式应用、避免闪烁、浏览器支持
2. 总结:本文介绍了声明式 Shadow DOM 这一网页平台特性,包括其定义、在客户端和服务器端的实现、与自定义元素结合使用、避免未样式化内容闪烁的方法、浏览器支持情况及 polyfill 构建,强调了其在服务器端渲染和样式处理方面的优势。
3. 主要内容:
– 声明式 Shadow DOM 介绍
– 是标准网页平台特性,允许服务器端渲染时使用
– 提供 HTML 中声明式定义 Shadow Root 的方法
– 构建声明式 Shadow Root
– 通过带有 `shadowrootmode` 属性的 `` 元素实现
– 纯 HTML 标记加载生成特定 DOM 树
– 与自定义元素结合
– 自动从静态 HTML 升级
– 构造函数中检查 `shadowRoot` 存在与否
– 相关限制与权衡
– 每个父元素一个 shadow root
– 无法从相同声明式 Shadow Root `` 初始化多个元素
– 流式传输与解析
– 直接与父元素关联,简化升级和附加过程
– 是 HTML 解析器的功能
– 服务器渲染与样式
– 支持内联和外部样式表,样式高度优化
– 避免未样式化内容的闪烁
– 介绍避免“未样式化内容的闪烁”的方法
– 功能检测和浏览器支持
– 检测浏览器支持情况的方法
– 构建简化的 polyfill
思维导图:
文章地址:https://mp.weixin.qq.com/s/XB7T_4ra5530VffBr5u9Ag
文章来源:mp.weixin.qq.com
作者:Jason、Mason
发布时间:2024/9/5 23:39
语言:中文
总字数:3863字
预计阅读时间:16分钟
评分:90分
标签:声明式 Shadow DOM,Web 组件,服务器端渲染 (SSR),Shadow DOM,前端开发
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
前言
Declarative Shadow DOM 是一项标准的网页平台特性,它允许在服务器端渲染时使用 Shadow DOM,提供了一种在 HTML 中声明式定义 Shadow Root 的方法,以及如何在客户端和服务器端实现组件的升级和样式应用,以及避免未样式内容的闪烁问题。今日前端早读课文章由 @飘飘翻译分享。
正文从这开始~~
庆祝:该网页特性现已在三大主流浏览器引擎中可用,并于 2024 年 8 月 5 日起成为基准的新增功能。
声明式 Shadow DOM 是一个标准的 Web 平台功能,从 Chrome 90 版本开始得到支持。请注意,该功能的规范在 2023 年发生了变化(包括将shadowroot
重命名为shadowrootmode
),该功能所有部分的最新版本规范已在 Chrome 124 版本中发布。
浏览器支持:
Shadow DOM 是 Web 组件的三个标准之一,另外两个是 HTML 模板和自定义元素。Shadow DOM 提供了一种将 CSS 样式限定在特定 DOM 子树内,并将该子树与文档的其余部分隔离的方法。<slot>
元素为我们提供了一种控制自定义元素的子元素在 Shadow 树中插入位置的方法。这些功能结合在一起,提供了一种构建自包含的、可重用的组件系统,该系统可以像内置的 HTML 元素一样无缝集成到现有应用程序中。
【第2379期】JavaScript是如何工作的:Shadow DOM 内部构造及如何构建独立组件
到目前为止,使用 Shadow DOM 的唯一方法是通过 JavaScript 构建一个 shadow root:
const host = document.getElementById('host');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
这种命令式 API 非常适合客户端渲染:定义自定义元素的同一个 JavaScript 模块还会创建其 Shadow Root 并设置其内容。然而,许多 Web 应用程序需要在服务器端渲染内容或在构建时生成静态 HTML。这对于为可能无法运行 JavaScript 的用户提供合理的体验来说是非常重要的。
服务器端渲染(SSR)的适用理由因项目而异。有些网站必须提供完全符合可访问性指南的服务器渲染 HTML,而另一些网站则选择提供一个无 JavaScript 的基本体验,以确保在慢速连接或设备上也能有良好的性能。
历史上,将 Shadow DOM 与 Server-Side Rendering 结合使用一直是比较困难的,因为服务器生成的 HTML 中没有内置的表达 Shadow Roots 的方式。此外,将 Shadow Roots 附加到已经渲染的 DOM 元素时也会对性能产生影响。这可能会导致页面加载后布局发生变化,或者在加载 Shadow Root 的样式表时临时显示未样式化的内容(“FOUC”)。
声明式 Shadow DOM (DSD) 消除了这一限制,将 Shadow DOM 引入服务器端。
如何构建声明式 Shadow Root
声明式 Shadow Root 是一个带有shadowrootmode
属性的<template>
元素:
<host-element>
<template shadowrootmode="open">
<slot></slot>
</template>
<h2>Light content</h2>
</host-element>
带有shadowrootmode
属性的模板元素会被 HTML 解析器识别并立即作为其父元素的 shadow root 应用。从上述示例中加载纯 HTML 标记将生成以下 DOM 树:
<host-element>
#shadow-root (open)
<slot>
↳
<h2>Light content</h2>
</slot>
</host-element>
此代码示例遵循 Chrome DevTools Elements 面板显示 Shadow DOM 内容的约定。例如,↳
字符表示插槽中的 Light DOM 内容。
这使我们能够在静态 HTML 中获得 Shadow DOM 的封装和 slot 投影的好处。无需使用 JavaScript 即可生成整个树,包括 Shadow Root。
【第2888期】使用Svelte来构建Web Component
组件的水合
声明式 Shadow DOM 可以单独使用,作为封装样式或自定义子元素布局的方式,但当与自定义元素结合使用时最为强大。使用自定义元素构建的组件会自动从静态 HTML 升级。引入 Declarative Shadow DOM 后,现在可以在组件升级之前为 Custom Element 添加 Shadow DOM 根。
一个从 HTML 升级的自定义元素,如果包含了声明式 shadow root,则该阴影根已经附加到该元素上。这意味着元素在实例化时将已经拥有一个shadowRoot
属性,而无需您的代码显式创建一个。在元素的构造函数中最好检查this.shadowRoot
是否存在任何现有的 shadow root。如果已经存在值,则表示此组件的 HTML 包含声明式 Shadow Root。如果值为 null,则 HTML 中不存在声明式 Shadow Root 或浏览器不支持声明式 Shadow DOM。
<menu-toggle>
<template shadowrootmode="open">
<button>
<slot></slot>
</button>
</template>
Open Menu
</menu-toggle>
<script>
class MenuToggle extends HTMLElement {
constructor() {
super();
// 检测是否已经有 SSR 内容:
if (this.shadowRoot) {
// 存在声明式 Shadow Root!
// 连接事件监听器,引用等:
const button = this.shadowRoot.firstElementChild;
button.addEventListener('click', toggle);
} else {
// 不存在声明式 Shadow Root。
// 创建一个新的shadow root并填充内容:
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = `<button><slot></slot></button>`;
shadow.firstChild.addEventListener('click', toggle);
}
}
}
customElements.define('menu-toggle', MenuToggle);
</script>
自定义元素已经存在了一段时间,直到现在,在使用attachShadow()
创建一个 shadow root 之前,并没有必要检查是否存在一个现有的 shadow root。声明式 Shadow DOM 包含一个小的更改,允许现有组件在没有 shadow root 的情况下正常工作:在具有现有声明式shadow root 的元素上调用attachShadow()
方法不会抛出错误。相反,声明式 shadow root 将被清空并返回。这允许未为声明式 Shadow DOM 构建的旧组件继续工作,因为声明式根在创建命令式替换之前得以保留。
对于新创建的自定义元素,有一个新的ElementInternals.shadowRoot
属性提供了一种显式的方法来获取元素现有的声明式 shadow root 的引用,无论是开放的还是关闭的。这可以用于检查并使用任何声明式 shadow root,同时在没有提供的情况下仍然回退到attachShadow()
。
class MenuToggle extends HTMLElement {
constructor() {
super();
const internals = this.attachInternals();
// 检查是否存在声明式影子树:
let shadow = internals.shadowRoot;
if (!shadow) {
// 不存在,创建一个新的影子树:
shadow = this.attachShadow({
mode: 'open'
});
shadow.innerHTML = `<button><slot></slot></button>`;
}
// 无论哪种情况,都连接事件监听器:
shadow.firstChild.addEventListener('click', toggle);
}
}
customElements.define('menu-toggle', MenuToggle);
每个父元素一个 shadow root
声明式 Shadow Root 只与其父元素相关联。这意味着 shadow roots 总是与其关联元素共存。这一设计决策确保了 shadow roots 像 HTML 文档的其余部分一样可以被流式传输。对于创作和生成来说,这也是方便的,因为将 shadow root 添加到元素中不需要维护现有 shadow root 的注册表。
【开源】无框架,跨框架!时隔两年,哈啰Quark Design迎来重大特性升级!
将 shadow root 与其父元素关联的权衡在于,无法从相同的声明式 Shadow Root<template>
初始化多个元素。然而,在大多数使用声明式 Shadow DOM 的情况下,这不太可能成为问题,因为每个 shadow root 的内容很少是相同的。虽然服务器渲染的 HTML 通常包含重复的元素结构,但其内容通常有所不同,例如文本或属性的细微差异。由于序列化的声明式 shadow root 的内容完全是静态的,只有在元素恰好相同时,才能从一个声明式 shadow root 升级多个元素。最后,由于压缩效果的影响,重复的类似 shadow root 对网络传输大小的影响相对较小。
将来,可能会重新考虑共享 shadow root。如果 DOM 获得对内置模板的支持,声明式 shadow root 可以被视为模板,并实例化以构建给定元素的 shadow root。当前的声明式 Shadow DOM 设计通过将 shadow root 关联限制为单个元素,允许未来存在这种可能性。
流式传输很酷
将声明式 shadow root 直接与其父元素关联简化了升级并将其附加到该元素的过程。声明式 shadow root 在 HTML 解析期间被检测到,并在遇到其开头的<template>
标签时立即附加。<template>
内解析的 HTML 直接解析到 shadow root 中,因此可以 “流式传输”:在接收到的同时渲染。
<div id="el">
<script>
el.shadowRoot; // null
</script>
<template shadowrootmode="open">
<!-- shadow realm -->
</template>
<script>
el.shadowRoot; // ShadowRoot
</script>
</div>
仅限解析器
声明式 Shadow DOM 是 HTML 解析器的功能。这意味着只有在 HTML 解析过程中存在带有shadowrootmode
属性的<template>
标签时,才会解析并附加声明式 shadow root。换句话说,声明式 shadow root 可以在初始 HTML 解析期间构建:
<some-element>
<template shadowrootmode="open">
shadow root content for some-element
</template>
</some-element>
为<template>
元素设置shadowrootmode
属性不会产生任何效果,并且模板仍然是一个普通的模板元素:
const div = document.createElement('div');
const template = document.createElement('template');
template.setAttribute('shadowrootmode', 'open'); // 这不会产生任何效果
div.appendChild(template);
div.shadowRoot; // null
为了避免一些重要的安全考虑,声明式 shadow root 也不能通过innerHTML
或insertAdjacentHTML()
之类的片段解析 API 创建。解析包含声明式 shadow root 的 HTML 的唯一方法是使用setHTMLUnsafe()
或parseHTMLUnsafe()
:
<script>
const html = `
<div>
<template shadowrootmode="open"></template>
</div>
`;
const div = document.createElement('div');
div.innerHTML = html; // 这里没有shadow root
div.setHTMLUnsafe(html); // 包含了shadow root
const newDocument = Document.parseHTMLUnsafe(html); // 这里也有
</script>
服务器渲染与样式
在声明式 shadow root 中,可以使用标准的 <style> 和 <link> 标签完全支持内联和外部样式表。
<nineties-button>
<template shadowrootmode="open">
<style>
button {
color: seagreen;
}
</style>
<link rel="stylesheet" href="/comicsans.css" />
<button>
<slot></slot>
</button>
</template>
I'm Blue
</nineties-button>
这种方式指定的样式也得到了高度优化:如果相同的样式表存在于多个声明式 Shadow Root 中,它只会加载和解析一次。浏览器使用单个支持的CSSStyleSheet
,由所有 Shadow Root 共享,消除了重复的内存开销。
可构建样式表在声明式 Shadow DOM 中不受支持。这是因为目前没有办法在 HTML 中序列化可构建样式表,也没有办法在填充adoptedStyleSheets
时引用它们。
如何避免未样式化内容的闪烁
在尚不支持声明式 Shadow DOM 的浏览器中,一个潜在的问题是如何避免 “未样式化内容的闪烁”(FOUC),即显示尚未升级的自定义元素的原始内容。在声明式 Shadow DOM 引入之前,避免 FOUC 的一种常见技术是对尚未加载的自定义元素应用display:none
样式规则,因为这些元素尚未添加 shadow root 并填充其内容。通过这种方式,在内容 “准备好” 之前不会显示内容:
<style>
x-foo:not(:defined) > * {
display: none;
}
</style>
引入声明式 Shadow DOM 后,Custom Elements 可以在 HTML 中渲染或编写,这样它们的 Shadow DOM 内容就可以在客户端组件实现加载之前就处于就绪状态:
<x-foo>
<template shadowrootmode="open">
<style>
h2 {
color: blue;
}
</style>
<h2>shadow 内容</h2>
</template>
</x-foo>
在这种情况下,“FOUC” 规则会阻止声明式 shadow root 的内容显示。然而,如果删除该规则会导致在不支持声明式 Shadow DOM 的浏览器中显示不正确或未样式化的内容,直到声明式 Shadow DOM polyfill 加载并将 shadow root 模板转换为实际的 shadow root。
幸运的是,这可以通过修改 FOUC 样式规则来解决这个问题。在支持声明式 Shadow DOM 的浏览器中,<template shadowrootmode>
元素会立即转换为 shadow root,从而在 DOM 树中不再保留<template>
元素。不支持声明式 Shadow DOM 的浏览器会保留<template>
元素,我们可以利用它来防止 FOUC:
<style>
x-foo:not(:defined) > template[shadowrootmode] ~ * {
display: none;
}
</style>
修订后的 “FOUC” 规则不是隐藏尚未定义的自定义元素,而是在遇到<template shadowrootmode>
元素时隐藏其后代元素。一旦定义了自定义元素,该规则将不再匹配。在支持声明式 Shadow DOM 的浏览器中,该规则将被忽略,因为<template shadowrootmode>
子元素在 HTML 解析期间被移除。
功能检测和浏览器支持
声明式 Shadow DOM 从 Chrome 90 和 Edge 91 起即可使用,但它使用了一个较旧的非标准属性shadowroot
代替标准化的shadowrootmode
属性。Chrome 111 和 Edge 111 中提供了新的shadowrootmode
属性和流式行为。
作为一个新的 Web 平台 API,声明式 Shadow DOM 目前尚未在所有浏览器中得到广泛支持。可以通过检查HTMLTemplateElement
原型中是否存在shadowRootMode
属性来检测浏览器支持情况:
function supportsDeclarativeShadowDOM() {
return HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode');
}
Polyfill
为声明式 Shadow DOM 构建一个简化的 polyfill 相对简单,因为 polyfill 不需要完全复制浏览器实现所关注的定时语义或仅供解析器使用的特性。为了 polyfill 声明式 Shadow DOM,我们可以扫描 DOM 以扫描所有带有shadowrootmode
的<template>
元素,然后将其转换为与其父元素相关的附加 shadow root。此过程可以在文档准备好后进行,也可以由更具体的事件触发,如自定义元素的生命周期。
(function attachShadowRoots(root) {
root.querySelectorAll("template[shadowrootmode]").forEach(template => {
const mode = template.getAttribute("shadowrootmode");
const shadowRoot = template.parentNode.attachShadow({ mode });
shadowRoot.appendChild(template.content);
template.remove();
attachShadowRoots(shadowRoot);
});
})(document);
关于本文
译者:@飘飘
作者:@Jason Miller、@Mason Freed
原文:https://web.dev/articles/declarative-shadow-dom
这期前端早读课
对你有帮助,帮”赞“一下,
期待下一期,帮”在看” 一下 。