Web Components 似乎要从悬而未决,终于快要被广泛接受了。对前端开发者而言,它们就像 Linux 桌面的那一年一样。我一直在阅读关于 Web Components 的最新文章,希望能找到我错过的某些内容,或者它们现在变得更加实质化,但我总是感到失望。我在2020年撰写了一篇关于 Web Components 的文章,感觉这段时间内关于它们的讨论并没有取得进展。人们似乎总是回到 Web Components 最初的承诺,尽管现实早已表明其未能兑现。
问题在哪里?
总结一下我之前的观点:
- Web Components 的宣传是“从整个网络获取语义元素!” 但这是试图解决的错误问题。
- 自定义元素不是“语义化”的,因为搜索引擎不知道它们的含义。
- “从整个网络获取”对最终用户性能总是比每个站点使用一个一致的、支持渐进增强的 JavaScript 框架更差。
customElements.define
是一个非常笨重的 API。<template>
和<slot>
可以,但不能完全替代真正的模板语言。Shadow DOM
也很笨重,解决了一个非常狭窄的问题。
好了,这些是基础知识,我们能不能就此达成共识呢?
是的,有些情况下使用customElement API
是一种方便的方式,可以对事物进行命名空间处理,并确保惰性加载的元素的构造函数被调用,但它们主要用于在富文本内容区域中呈现第三方嵌入内容之类的场景。这并不能构成“Web Components”这一术语指代一个有实际意义的东西。
如果你在基于轻量级框架的 JavaScript 小部件集合上放上“Web Components”的名字,相比于使用其他名字,更多的人会查看它。然而,最终这只是一些笨拙的 DOM API 的市场营销名称,以及我们都希望真的存在的梦想。这个梦想是,你可以轻松混合和匹配小部件,而不考虑每个小部件是用哪个框架编写的,但事实上,这样做的唯一方法是为页面上包含的每个 Web 组件都支付包括一个框架的全部代价。这永远不会是在终端用户性能至关重要的网站上的实际选择。
浏览器支持
好消息是,尽管“Web Components”实际上并不是一个真正的东西,但浏览器制造商最终会以正确的方式解决实际问题,只是有时候需要一些时间。
正如我当时对 Shadow DOM 的描述:
Shadow DOM 的根本作用是允许页面的某个元素拥有自己的 CSS 重置。实际上,我们也可以将这作为 CSS 本身的一部分(也许通过更改 @import 的规则,允许它嵌套而不仅仅是在顶层)。
事实证明,这确实发生了,因此一旦对环形作用域的浏览器支持足够广泛,就没有太多使用 Shadow DOM 的原因了。使用 CSS 作用域选择器和导入层,创建一个仅针对您的组件的 CSS 重置,您将获得所有 Shadow DOM 的好处,而无需关心“light DOM 样式穿透 shadow root”之类的术语。
那么,浏览器制造商应该做的是放更多的努力到“Web Components”本身,而不是什么呢?
我认为,历史上铺设 DOM 的最佳示例就是 jQuery 和 querySelectorAll 之间的关系。在 2003 年,Simon Willison 制作了一个 document.getElementsBySelector 的演示版本。JQuery 将这个想法转化为曾经广泛使用的 $(“”) API。浏览器制造商接受了这个想法,将其变成了 document.querySelectorAll,使其更快速、普遍可用,而不必链接到 jQuery。
这是 Web 进化的理想情景:开发人员在一系列年份内共同努力,找到了解决一个重复问题的方案,然后浏览器制造商将该解决方案变成原生的,以便普遍可用和更高性能。相比之下,customElement 和 Shadow DOM API 大约是在十年前诞生的,早于许多现代 JavaScript 框架的技术。因此,由于当时我们作为一个行业在构建大型 JavaScript 应用程序框架方面的经验不足,它们最终解决了错误的问题。
与其试图改进现有的“Web Components”,浏览器制造商应该专注于找到我们可以标准化开发人员已经在做的新领域。我有三个建议,他们应该关注什么。
近况
最近,Nolan Lawson写了一篇《让我们通过构建一个现代 JavaScript 框架来学习它的工作原理》的文章。在文章中,Lawson将“现代”框架定义为具有以下三个组件:
- 使用响应性(例如信号)进行 DOM 更新。
- 使用克隆的模板进行 DOM 渲染。
- 使用现代 web API,如 和 Proxy,使上述所有操作更容易。
老实说,在 Lawson 的文章发布之前,我已经开始起草这篇博客文章,所以很高兴看到他突出了我打算在这里写的现代框架的一些方面。<template>
和Proxy API
是可以接受的,而且不需要浏览器制造商特别改进。但在响应性/信号和 DOM 渲染方面仍有很大的改进空间。让我们先谈谈响应性。
响应性的基本思想是,如果你有一个简单的组件,比如一个待办事项,你希望知道 todo.done 从 false 变为 true 时,以便触发对其他数据(比如 todos.count 从 N 变为 N+1)的更改。在我 2020 年的文章中,我将此称为“数据生命周期”。
在 Lawson 的文章中,他使用大约 50 行 JavaScript 创建了一个快速而脏的响应性系统,但更现实的响应性系统,如 observable-membrane、@vue/reactivity 和 @preact/signals,则显着更大更复杂。
将这些系统的核心从 JavaScript 库移到浏览器中将允许对响应性数据元素之间的依赖树进行显着优化。为了将节点标记为脏的性能优化算法,需要大量代码。对于只有少量响应式元素的简单待办事项应用程序,响应性引擎的复杂性很容易超过应用程序本身的复杂性。但是,如果浏览器直接解决这个问题,那么可以在 C++ 或 Rust 中编写这些代码,并使其达到最大效率,因为它不需要在每次页面加载时打包、发送到客户端并从头开始解释。这个成本可以由浏览器团队支付一次,而所有的 web 开发者和最终用户都可以受益。
这显然是多个框架独立解决的问题。现在是浏览器着手解决它的时候了。
更新:Dave Rupert 在 Mastodon 上指出,W3C 提出了向 JavaScript 添加响应式信号的提案,目前正在提交给 TC39。祝愿他们在提案过程中好运。
Morph DOM / Virtual DOM
这篇文章讨论了现代 JavaScript 框架中值得优化的另一个方面,即 DOM 渲染。文章提到,虽然 Nolan Lawson 在他的文章中花费了大量时间解释如何将代理调用转换为高效的 DOM 更新,但他认为浏览器制造商不应介入模板争论。他认为现有的 JavaScript 模板文字语法足以让开发人员创建适合自己的模板系统,或者他们可以继续使用像 JSX 这样的解决方案,使模板成为其框架的编译时特性。
文章认为需要的是一种有效的方法,将模板系统的 HTML 结果与页面的现有浏览器 DOM 合并。对于简单的 HTML 替换,写入 node.innerHTML = newHTML
; 在性能上是可以接受的。问题出现在节点具有事件处理程序(可以通过向 DOM 更高层次委派来部分缓解)或需要在渲染之间保留的状态时。例如,如果有一个输入元素,对其父元素之一使用 node.innerHTML 将清除输入的文本并丢失输入框中输入的任何文本。所有用于模板化交互式页面的系统都需要一种方法,在每次渲染时防止发生这些问题,并仅覆盖应该被覆盖的状态。
文章提到 React 通过使用虚拟 DOM 来解决这个问题,然后将其与浏览器 DOM 协调。即使是像 HTMX 这样以服务器为重点的框架也需要一种在不清除当前元素状态的情况下交换 DOM 节点的解决方案。在这方面的另一个解决方案是 MorphDOM,它已有八年历史。最近,Caleb Porzio 一直在致力于 @alpinejs/morph
作为解决方案,并偶尔在播客中讨论他在处理困难边缘情况时遇到的问题。Svelte 通过将尽可能多的协调过程推到其编译器中来解决 VDOM 协调问题,但即使对于 Svelte,预先执行的事情也有一些限制,有些事情需要在客户端完成。不管你如何解决这个问题,创建两个 DOM 树之间的差异可能是一个非常复杂的过程,因为在确定节点是否完全替换或只是移动到树中的另一个位置时存在困难。有很多边界情况需要考虑和权衡。这是另一个浏览器制造商可以进行整合的领域。如果浏览器有一个 API 能够协调两个 DOM 树,它可能会更快、更高效。将其作为浏览器 API 允许框架停止在渲染性能上进行差异化,而是专注于基于其 API 的便利性和生态系统深度进行差异化。这是一个已经准备好被铺设的领域。
自适应大小的 iframe
让我们谈谈劳森文章中未提到的另一个常见的开发者需求以及我认为因被视为遗留技术而从许多开发者的视线中消失的技术。
虽然 humble iframe 可能可以追溯到 Internet Explorer 4,但它实际上比 Shadow DOM 更强大,因为它们具有更强的样式隔离和一些真正的安全性保证。因此,虽然 iframe 在现代 JavaScript 框架中基本上没有作用,但它们在我所在的在线新闻发布领域仍然非常普遍。如果您有一个内容容器,希望能够在其中放置某种小部件并使其在 CMS 和将来的重新设计中正常工作,那么您会使用 iframe。这些示例包括 DataWrapper 和 Flourish 的图表,以及 YouTube 和 Vimeo 的视频。
iframe 的主要问题在于 CSS 中没有一种告诉 iframe 采用其内容高度的方法。这样做有很好的理由。例如,如果宿主页面可以通过添加 iframe 并查看其高度是已登录页面还是已注销页面来了解您是否已登录到银行网站,那将是不好的。因此,使响应式大小的 iframe 的最佳方法是使用 JavaScript 从包含的页面传递消息到宿主页面,告诉宿主页面包含页面的大小,以便宿主可以相应地调整 iframe 的大小。人们使用各种库来执行此操作,如 NPR 的 Pym.js 和 SideChain,或者 @newswire/frames
。
尽管存在安全问题,但我认为这可以通过在框架和框架化页面中使用 X-Frame-Options 标头或 CSP 来告诉浏览器哪些站点希望在允许动态框架高度的问题上进行合作。将此从 JavaScript 移到浏览器中将修复许多因轮询窗口大小更改或包含带有 vh CSS 样式的页面而导致的小问题,并使那些需要创建或使用嵌入内容的人更容易处理。这还将使在不允许 JavaScript 或需要严格限制 JavaScript 的情况下使用响应式 iframe 成为可能。
总结
我希望大家都理解我所提出的观点。Web Components 的梦想是一个不错的梦想,但我们需要新的解决方案来应对当代 web 开发者遇到的问题,而不仅仅是对十年前的路径进行持续的翻新。
我请克劳德为这篇博客写一个令人惊叹的结论,以下是它的观点:
“Web Components” 的承诺在整个网络上引起了人们的想象,但却未兑现。与其试图将新的 API 强行套用到旧问题上,我们应该展望未来,追随开发者已经描绘出的愿望线。
响应性、虚拟 DOM 协调以及自动调整大小的 iframe 都是开发者多年来为找到解决方案而努力工作的领域。现在是浏览器制造商铺平这些道路的时候了——借助开发者已经创造的最佳内容,使其更快、更高效,并普遍可用。
这就是在网络上实现真正进步的方式——不是从崇高的自上而下的理念开始,而是从实际需要中自发出现的真正解决方案。如果浏览器制造商能够转向启用开发者已经在做的事情,我们将为每个人提供更好的工具。解决方案可能不总是引人注目,但它们将扎根于实用性。
因此,让我们从过去吸取经验,接受当下的现实,并构建我们需要的网络——而不是我们想象中的网络。如果我们有远见,这些组件已经在逐渐形成。
原文:https://blog.carlana.net/post/2023/web-component-alternative-futures/