几年前我写了一篇文章,建议 Web Components 可能不是 Web 开发的最佳发展方向。
## 也许 Web Components 不是未来?Ryan Carniato ・ 2020年3月27日 #webdev #html #javascript
这是一种温和的审视,看看哪些地方有意义,哪些地方行不通。这不是一场“我们对抗他们”的争论,我希望人们能自行得出合理的结论。
但是在这过去的几年里,我发现情况越来越糟糕。有些人可能从未认真对待过Web组件,但我一直都很重视。我在生产环境中使用它们已经有7年了。在早期,我还为一些功能如Shadow DOM编写了几种polyfill,以便在它们成为主流之前就能使用它们。
但无论是当时的经历还是之后的经历,都只让我得出了一个结论:Web Components 可能是我能看到的对 web 未来构成最大风险的技术。
乌托邦的愿景
我承认这个说法听起来有点过于强硬。但有很多理由让我相信这一点,而这一切都始于理解Web Components所提出的益处。
Web Components 的愿景是,有一天无论你使用什么工具来编写它们,你都可以拥有像 DOM 元素一样原生的组件,这些组件可以添加到任何网站中,而无需考虑网站是如何编写的。无需担心特定的构建工具、特定的运行时机制,或者与这些组件交互的方式。
从某种意义上说,一个便携且互操作的 web。一个能够减轻未来迁移需求的 web。一个为你准备好任何可能未来的 web。保护你的网站和应用程序免受未来变化影响的完美方式。
这不是很吸引人吗?这也是为什么Web Components的前景如此诱人却又如此危险。
对立的标准
我不需要引用旧的xkcd漫画来让你知道标准带来的挑战。标准越雄心勃勃,就越有可能遭到反对或出现替代方案。标准化并不能消除这些问题。它只是表明有一种被认可的方法。你可以接受它,也可以不接受。
如果JavaScript框架的数量众多是任何指标的话,我们离在如何在网络上编写组件上达成一致还差得很远。即使我们今天稍微接近一些,十年前我们也远未达到这个程度。
引入更高层次的原语可以产生积极的影响。突然间,一些原本难以完成的事情变得更容易了。这最初导致了更多的探索。Web Components 在2010年代中期导致了 JavaScript 框架数量的增加。它是促使我创建 SolidJS 的一个重要灵感来源。类似地,由于 Vite,构建 Metaframeworks 的数量也有所增加。
但它也可能产生负面影响。如果做出太多假设,就更难探索替代方案,因为一切都围绕着既定的事物。还有什么比一个永远不能改变的 web 标准更固定的呢?
我在 web 标准之外对此深感困扰。根据规范,JSX 没有明确的语义,但试着说服各种工具它们假设得太多了。如果 JSX 在浏览器中被标准化,那将是一场噩梦。忘记像 Inferno、Solid 和 Million 这样的框架是如何通过它们的 JSX 转换做得更优化的事情,就连 React 也随着时间的推移改变了它们的转换。
这只是众多例子中的一个。那些帮助我们的东西,有时会有效地束缚我们的手脚。在标准化任何更高层次的机制时,我们必须谨慎行事,因为这会做出很多假设。不能仅仅说不是每个人都必须使用它,因为它的存在会影响我们对整个平台的看法。
机会成本是真实的
作为一名框架作者,我对此深有体会。我经常说,在这个领域里,很多东西是被发现的,也有被发明的。我的意思是,设计决策中存在某种真理或物理定律,遵循这些规律会引导我们走向相似的地方。并不是说这些工具互相抄袭,而是它们都顺应了这些规律。
而且出于同样的原因,一旦有了改变我们看法的发现,损害就已经造成了,甚至在我们编写任何一行代码之前就已经发生了。如果你的工作是设计一个系统,你不想有冗余的部分。你希望有清晰且有目的的边界。与其制作一百万种相同事物的变体,你更希望重用同一个事物。更重要的是,认识到为了完成常见的任务,你需要多个事物,这些部分就会变得相互交织。
例如,React 开发者肯定感受到了从 2017 年宣布 Suspense 到 2022 年我们终于得到了 RSCs 的数据获取故事之间有多长时间。为什么这花了 5 年时间?因为这不是一条直线。花了很长时间才理解所有这些部分是如何拼接在一起的。这本身是合理的。但更重要的是,React 不想在他们知道有一个完整的解决方案之前分阶段发布。随着他们研究得越来越多,发现所有这些部分都是相关的,虽然它们可以被拆分,但它们需要彼此来描绘整个图景。
RSCs 并不符合每个人对 React 中使用 Suspense 进行数据获取的看法。也许人们本可以从一个客户端数据获取的基本功能中受益。React 在这里选择了一个雄心勃勃的方向,这是他们作为工具的权利,但他们决定什么是最好的,这可能有多种不同的结果。
作为开发者,我可以选择不使用React。虽然我可以选择不使用React的某些特性,但很明显,React中的所有内容都已经适应了他们当前的思维模型。我甚至可能希望可以轻松地迁移到React之外。
但是这里有一个很大的区别。React 是一个库,并不是一个标准。当涉及到标准时,这些选项并不相同。如果我只想使用作用域样式,而现在却不得不处理 Shadow DOM,因为这是由于 Web 组件而最适合单一实现方式的抽象,那我只能接受这个现状。
当原始组件超出预期用途,或者过度抽象化时,你就无法回头了。你已经为此付出了代价。任何曾经对项目进行过重大架构重构的人都可以证明,最难的部分是调整边界。如果事物属于相同的逻辑分组,那么更新起来会更容易。当你需要将事物拆分开来时,事情才会变得真正具有挑战性。你可以随时添加另一层抽象来解决问题,但移除一层抽象则可能很困难。
抽象的成本
所以Web Components的基本问题在于它们是基于Custom Elements构建的。元素不等于组件。更具体地说,元素是组件的一个子集。可以说,每个元素都可以是一个组件,但并非所有的组件都是元素。
那么呢?这意味着每个界面都需要经过DOM。有些是以定义明确但并不完全契合的方式进行,而有些则是以新定义的方式进行,这些新方式增强了或改变了处理元素的方法,以适应扩展的功能。
首先,DOM元素具有属性和属性值。这样它们可以表示为HTML。属性只能接受字符串,而属性值作为JavaScript接口可以处理任何类型的值。原生DOM元素在特定属性/属性值上有很多规则,例如有些是布尔值(存在即表示应用),而有些是伪布尔值(需要明确的"true"/"false")。有些属性值会反映到属性上,而有些则不会。
模板语言的一个目标是以统一的方式解决这个问题。我们可以为已知的元素和属性制定特殊规则。但是,对于自定义元素,我们并不确定。这就是为什么一些模板库使用有趣的前缀来指示应该如何设置的原因。即使在Solid的JSX中,我们也使用了attr:
、prop:
和bool:
前缀来达到这个目的。现在,每个运行时位置和编译器钩子都需要意识到这一点。
你可能认为我们需要一个更好的模板标准。但是,就像上面提到的JSX,你需要考虑这一决定的后果。几年前,大多数人可能会认为像LitHTML这样的模板渲染方式是一个好方法。其他解决方案也可以输出同样的结果。然而,在这段时间里,我们意识到像Solid这样的响应式渲染方式表现更佳。它通过改变模板的语义来实现这一点。如果我们继续推进,我们可能会有一个并不是最佳模板方式的标准。
这还不仅如此。DOM元素可以被克隆。但是自定义元素具有不同的行为,这意味着它们应该被导入而不是克隆。它们具有基于DOM的生命周期,这些生命周期可以在升级时同步或异步触发。这会对诸如响应式跟踪和上下文API之类的功能造成影响。然而,这些细节对于与原生DOM行为和事件进行交互来说是重要的。而这些细节都是JavaScript组件不需要关心的。
还有一些特例,比如Shadow DOM中事件目标的工作方式。有些事件不会“组合”,即不会冒泡超出Shadow DOM的边界。有些事件不会一致地冒泡,因为它们不识别不同的目标,例如在"focusin"事件中,因为Shadow Host总是被设定为目标,无论哪个子元素获得焦点。我们可以就此讨论几天,但我在这里不想过多偏离主题。有些问题是今天的不足之处,有些则是设计上的选择。但共同点是,它们都需要特别考虑,否则就不会必要。
当然,这会有性能开销:
## UI组件的真实成本再探讨 Ryan Carniato 为这是学习 ・ 2021年6月25日 #javascript #webdev #性能 #webperf
但是即使你认为这种性能损耗微乎其微,当我们转向服务器端进行如SSR(服务器端渲染)等操作时,这又将我们置于何种境地呢?是的,你可以完全使用Web Components进行SSR。hydration(水合)是完全可以实现的。但是,它们是一个DOM接口,却存在于没有DOM的地方。你可以创建一个最小的包装器来处理大多数事情,但这都是额外的开销。这一切都是因为我们试图让组件做它们本不该做的事情。
在服务器端并没有这样的标准。我们又回到了特定解决方案的局面。这只不过是一种类型的框架,并不能提供比我在下一个项目中选择 Vue 或 React 更多的保证。这并没有什么问题,但我们需要认识到这一点。
Web Components 的真正成本
整个情况是,处理原生元素的复杂性增加,以适应 web 组件的新灵活性。随着工具对它们的支持越来越好,不仅使用者为此付出了代价,所有使用该工具的用户也都在付出代价。需要传输和执行更多的代码来检查这些边界情况。这是一种隐藏的税收,影响了所有人。
我谈到了早期标准化可能会带来灾难的地方。但它也有潜力在某些路径上抑制未来的创新,因为它假设得太多。关于hydration的改进,比如Resumability、Partial或Selective Hydration,这些都依赖于事件委托来工作。但如果Shadow DOM干扰了这一点,那么Web Components如何适应这种模式呢?有人可能会说,SSR在2013年时我们并没有想到这一点,因此这是一个疏忽,但这种差距随着时间的推移只会越来越大。
如果从编译器和构建工具的进步来看,我们正朝着这样的方向发展:组件不仅仅是一个开发体验方面的考虑。在编写代码时你可能会用到它们,但它们不会出现在最终输出中。为了获得最佳用户体验,我们会优化掉这些组件。
## 组件完全是多余的开销 Ryan Carniato 在 This is Learning ・ 2021年5月10日 #javascript #webdev #react #svelte
我不是说没有人能找到这些问题的一些有趣解决方案,但所有这些解决方案都意味着要承担由于基础抽象不正确而导致的隐性成本。这就是这里对话如此困难的原因。这不是你可以改进的问题。这只是许多事情中的一个错误。
现在我们可以都说不同的解决方案有不同的权衡。也许像 Web Components 这样的技术只有在得到像标准组织这样的支持下才能成功,因为它的普及程度需要非常高才能发挥作用。这是我们努力追求的理想,但目前还没有完全实现。
但这样真的理想吗?
乌托邦再探
当我们讨论微前端或微服务时,经常会遇到类似的争论。从组织的角度来看,将项目与开发人员的工作进行对齐是有益的。这遵循了康威定律。
Web Components 提供的隔离或便携性意味着页面上可以有来自不同来源的多个不同组件。不过,像对待其他技术一样,谨慎行事是必要的。就像不希望将所有的微服务都用不同的语言编写一样,你可能也不希望将所有的组件用不同的框架编写。
但是前端是一个更为严格的领域。每千字节JavaScript的成本并不小。不仅仅是维护方面,你不希望混合使用不同的框架,还为了减少加载量。而这正是问题开始的地方。
如果你的目标是面向未来,那么你需要准备好在同一页面上保持相同库的不同版本。这不仅适用于Lit,也适用于React。你可以选择从不更新任何内容,但这在没有Web组件的情况下也是一个选项。这里没有额外的保证。面向未来唯一的办法是冻结现有内容、维护多个版本,并在你的页面上加载更多的JavaScript。
实际上,你会同步更新你的库,这与其他库也是一样的。在这种情况下,如果你的页面上只有一个库,Web Components 并没有为你做任何事情,反而增加了更多的开销。可能还会妨碍库当前和未来提供的功能。你还不如不使用 Web Components 直接使用库。
第二个考虑是粒度。如果你有一个微前端,那么它就是一个可以独立替换的组件。如果你将来决定这不是处理问题的最佳方式,你可以将其替换掉。但是,一旦你在所有地方采用Web组件,你就需要解决每个接触点。Web组件与微前端和微服务不同,因为它们可以跨领域使用。这对标准化是有好处的,但我从未见过任何使用jQuery的公司能够完全摆脱它。
Web Components 最具吸引力的用途是作为某种微前端容器。在这种情况下,你不需要承担扩展成本,外部通信也最少,而且它们很容易替换。一次性场景。然而,在这些情况下,摩擦力已经很低,以至于使用 Web Components 并非必要。我会为了将 Zendesk 小部件添加到我的页面而使用它们,但这种抽象是否值得付出代价?
结论
这正是问题的关键所在。我不能否认在某些场景下使用Web Components确实有一些ergonomic(使用上的)好处。但是它在所有地方带来的成本是相当高的。虽然我不应该急于因为一项技术允许出现不良模式而否定它,但在它从未符合理想场景的情况下支持它确实很难。最好的情况下,它也只是带来了一些名义上的额外负担。
Web Components 是一种妥协,从头到尾都是妥协。正如我们所知,有时候我们需要妥协。但这没什么值得兴奋的。而且有些妥协比其他的更好。
有人告诉我,十年后可能就没人再使用Solid了,但Web Components仍然会存在,界面和今天一样。但我想了想,还是决定用Solid来构建它,因为这是今天最好的选择。即使十年后我只能使用一个十年前的Solid版本,那也比使用Web Components版本要好。十年的时间不会抹去这一点。希望十年后我能用上更好的东西。
这个决定是完全独立的。从某种意义上说,Web Components 本身并没有什么问题,因为它们只能是它们现在的样子。危险之处在于它们承诺成为某种它们实际上不是的东西。它们的存在方式扭曲了周围的一切,这使得整个 web 都处于风险之中。这是每个人都要付出的代价。
共同学习,写下你的评论
评论加载中...
作者其他优质文章