前段时间,Dave Rupert 的文章《既然 Web 组件这么能打,我为什么不愿使用?》引起了不小的轰动。我自己使用 Web 组件也有几年时间了,所以想就这个话题掺和几句。
为了防止工程师大佬们说我有失偏颇,这里我先澄清一下:我觉得 Web 组件确实有利有弊,要不要用当然是个需要权衡的辩证过程。所以这里我们先分享几个 Web 组件发挥出色的案例,然后再讨论它们可能在哪些场景下表现不佳。
客户端渲染的叶组件
对我来说,这明显就是 Web 组件最能大展拳脚的应用场景了。假设我们在 DOM 树的叶分支上有一些组件,它们不需要在服务器端渲染,而且内部不<slot>任何内容。这方面示例包括:富文本编辑器、日历小部件、取色器等。
也就是说,我们已经直接回避了 Web 组件的那些天然弱项,比如服务器端渲染(SSR)、水合、slotting,甚至还有 Shadow DOM。如同大家没有使用框架,或者选择的框架直接支持 Web 组件,那只需要在模板或 JSX 当中添加<fancy-component>标签,即可达成预期效果。
例如,以我自己的 emoji-picker-element 为例。只需一行 HTML 即可将其导入:
再用一行就能使用:
不涉及捆绑器、不涉及转译器,也不需要框架集成,直接复制粘贴即可。这就跟过去的 jQuery 插件一样。另外,我也见过在复杂 SPA 项目这么用的情况,Web 组件同样可以正常起效。
这也是 Web 组件在当初设计时的初衷,即应该能像使用内置 HTML 元素那样轻松使用 <fancy-element> 。
粘合代码,或者避免大规模重写
想象一下:我们有一套庞大的 React 代码库,它已经在业务系统中运行了一段时间。现在,团队想要迁移到 Svelte,那唯一的办法就是做整体重写了呗?包括为之前使用的所有第三方组件寻找相应的 Svelte 版本?
不少前端开发者确实是这么理解框架的,即从一种框架迁移至另一框架时会产生巨大的切换成本。但这其实是对 Web 组件的重大误解,并不存在换框架就得换组件这种事情。
更直白地讲,Web 组件的全部意义就是把我们从这种混乱当中解放出来。如果大家决定从 Vue 切换至 Lit,或者从 Angular 迁移到 Stencil,那么对所有组件做整体重写肯定要带来很多不必要的痛苦。
正确答案是,旧代码完全和新代码和谐共存。之后,我们利用 Web 组件的互操作性将二者粘合起来,即可回避大规模重写原有内容:
Web 组件可以向下传递 props/attribute,并向上发送事件。(这正是 Web 组件的意义所在。)如果你的框架支持 Web 组件,那这一切都可以开箱即用。(如果不支持,也可以编写一点简单的胶水代码。)
我知道,有些朋友特别讨厌两套框架共存的情况,但我觉得这种判断大多是基于直觉、而非客观事实。公平地讲,如果大家的项目会使用元框架来进行 SSR/水合,那这种部分迁移在实际执行时确实可能难度很大。但 Web 组件最为擅长的,就是通过定义高级契约在客户端上把两个组件整合起来。
所以如果大家厌倦了每年都要重写整个 UI(或者老板不喜欢这样没完没了地折腾),那 Web 组件真心值得推荐。
设计系统和大企业需求
看看 Cassondra Robert 在 CSS Day 大会上的演讲,特别是他展示的一张演示文稿,就能感受到 Web 组件在重量级企业中的渗透程度:
除此之外,未列出的支持者还有甲骨文、SAP、ServiceNow 等等
大家可能已经注意到,很多大企业都在悄悄使用 Web 组件,特别是在各种内部设计系统和组件库当中。各位只要多在 webdev 社交媒体上逛逛,就会发现 Web 组件的普及度之惊人。另一个重大发现,就是根据某些量化指标,React 支持的页面加载占比约为 8%,而 Web 组件的使用率已经达到 20%。
问题在于,很多大公司并没有在社交媒体(Twitter/X、Reddit 等)上大肆宣扬自己在使用 Web 组件,或者指导大家怎么使用 Web 组件。另外,Twitter 上的很多技术大牛一直忙于介绍 React 各个小版本及生态系统中的新生内容。这种情况也有道理,毕竟大企业的讨论往往集中在内部,向外界传达的信息相对有限。而小公司(代理机构、初创企业和自由职业者)虽然体量不大,但在社交媒体上却更为活跃。如果即便 Web 组件在大厂当中更为活跃,外部视角下也未必显现得出来,至少他们不会在 Twitter 上反复强调。
那么,为什么大企业会如此热衷于 Web 组件呢?一方面,是因为 Web 组件在设计上能够在各种环境下顺利起效。大企业的前端往往由 React、Angular、Ember 和静态 HTML 共同编写而成,而且这一切还得跟公司的主题和品牌形象完全契合。前面提到的大规模重写对于小体量的初创公司可能是场有趣的演习,但在大厂层面上还是少搞为好。
再有,大厂的代码库需要面对更多用户、贯穿的时间尺度也更长久,所以必然受到各种不同技术决策的影响。在我看来,企业偏好 Web 组件的主要原因也正在于此:稳定性更好,寿命更长。
我们不妨设想各种常见的 React 代码库,包括对各种依赖项(React Router、Redux 还有 React 本体)的更新,是怎么耗费几个礼拜时间的重写来适应重大变更的。海勒姆法则认为当一个 API 有足够的用户的时候,在约定中做出的具体承诺将变得无所谓,系统中被观察到的所有行为都会被部分用户直接依赖。这话一点没错,海勒姆法则在企业规模上必然出现,而哪怕是一点点微小变化也可能引发蝴蝶效应、破坏数千个组件,甚至小版本变更也会带来几个礼拜的测试、验证和记录。事实证明,React 的每次小版本更新都是对业务系统的考验,而大版本升级更是堪称灾难。
相比之下,Web 平台领域也有出色兼容性的绝佳案例——1996 年上线的 Space Jam 网站至今仍在运行。这段稳定性传奇的背后正是 Web 组件的力量,这对没办法每隔几年就重写前端的公司来说当然是种巨大的优势。
在使用 Web 组件时, connectedCallback 就只是 connectedCallback,绝不会改变。无论我们把怎样的代码委托给浏览器,之后都可以历经多年不做维护或验证。具体来讲,我们实际上是把这份责任委托给了谷歌、苹果和 Mozilla。
大企业的风格就是缓慢、谨慎、厌恶风险——Web 平台也是一样。正因为如此,Web 组件才迅速席卷了整个企业世界。
Web 组件的短板
当然,Web 组件的一切优势背后,都有着与之对应的短板。下面来看 Web 组件做得不好的几个方面:
服务器端渲染(SSR)。我觉得这个问题在 Web 组件领域长期没能得到解决。当然,我们已经有了声明式 Shadow DOM,但它本身也成了难题的一部分。由于服务器端的 Web 组件渲染缺少标准,所以每种框架的实践都略有不同。这一点从 Lit SSR 仍归于“Lit Labs”就能看出点端倪。也许未来,我们可以在服务器上渲染三种不同的 Web 组件框架,并把它们顺利对接并混合起来,到那时候这块短板才算真正被补齐。但我个人认为,这个目标至少还要几年时间才有可能达成。
可及性。我们还是没法让 ARIA 引用轻松跨越阴影边界,对话框和焦点的处理也比较麻烦。总之,如果不想把可访问性搞得一团糟,那我们还是得从起步阶段就认真规划组件结构。这个问题的解决还有很长的路要走,而且 2023 年之内恐怕还不会有多少进展。
除此之外,锁定问题(例如元框架、捆绑程序、测试运行程序)、IE11 的持续遗留问题(这已经成了很多朋友的心理阴影,一看到 #useThePlatform 就浑身发抖),还有固守的排他性心态(我学的是 React,它很好用,所以我不想再学别的)也都客观存在。我只能说我自己对 Web 组件很满意,但并不是每个人都买它的账。但 Web 就是这样一个庞大的世界,不同的人用它做不 同的事,正是如此现实才让它展露出精彩纷呈的形态。
总结
总之,关于 Web 组件,大家想用就用、不想用就放下。或者再过上几年,等功能和 Web 标准更完善之后,咱们再回来试试。
我觉得 Web 组件很酷,但也理解为什么很多人对它毫不感冒。我不会为了推广 Web 组件而到处去布道,它们只是工具箱中平平淡淡的一个选项。如果它正好能帮你解决问题、自身短板又不会造成困扰,那就值得一试。
我最喜欢 Web 组件和 Web 标准的一点,就是可以把一大堆无聊的问题都托付给浏览器。怎么组合组件?如何确定样式范围?要怎样传递数据?管他呢,让浏览器去操心就好。如此一来,我就能把更多时间花在对最终用户真正重要的问题上,比如性能、可及性和安全性等等。
在 Web 开发当中,我经常觉得自己努力解决的根本就不是问题本身,而是问题附带而来的那些无关复杂性。我经常得在 npm 依赖项、状态管理调试,还有测试运行器为什么不能跟 linter 配合良好等麻烦事上浪费精力。其实解决这些问题也有爽感,很多朋友愿意亲自动手,我有时候也有这种感觉。但归根结底,这些纯粹是满足虚假成就感的无意义工作,毕竟最终用户根本就不关心你的捆绑器是否与 TypeScript 转译器保持同步。
现如今已经是 2023 年了,但使用 Web 组件还是会带来额外的复杂性,比如前文提到的 SSR 和可及性问题。任何一点处理不好,都会给最终用户带来感受得到的真实影响,所以大家要认真权衡这一切到底值不值得。
在我看来,选择 Web 组件仍然物有所值,但大家也可以有不同意见。“充分发挥 Web 的专长”这事虽然没啥吸引力,但却能切实帮我们解决实际问题。
网友评论
Jochen Kühner:喜欢你的组件,我试过,而且它确实能通过 NPM 在我的设计器里直接运行!(https://node-projects.github.io/web-component-designer-demo/index.html) 干得漂亮!
ED:对于声明式 Shadow DOM,我有一个疑问,不太明白你提到的问题。你提到 SSR(服务器端渲染)似乎还没有解决,具体指的是什么?实际上,服务器只需要发送页面结构,然后 Web 组件会在客户端自动完成更新(水合)。对我来说,重要的是客户端接收到的结构,不太关心这个操作在服务器上是如何执行的。如果你想了解更多关于我的观点,可以查看此链接:https://developer.chrome.com/en/articles/declarative-shadow-dom/
作者回复:确实,服务器端的 Web 组件输出应该有明确的标准格式,但实际渲染方法目前尚无通用标准。举例来说,对于需要事件调度的组件,服务器端如何处理呢?Node.js 缺乏 Event 对象和 dispatchEvent 方法。这使得服务器需要考虑是否要进行 polyfill,或者将事件视为类似于服务器上没有实际用途的 API,例如 getBoundingClientRect。
另外,考虑两个不同框架的组件在服务器端组合的情况。例如,当第一个框架遇到"other-component"标签时,服务器应该如何渲染它?难道要使用"document.createElement("other-component")"吗?然而,服务器端没有 document API,即使有,也难以确定哪个标签名称应该映射到哪个框架,或者应该使用哪种 API 方法(例如 Lit 中的 render,Stencil 中的 createRenderer 等)。
尽管 Web 组件社区已经开始讨论如何解决这些问题,但据我所知,不同框架之间尚未就这个问题达成一致的共识。
patricknelson:我认为这个话题非常有价值,其中最重要的观点是:我们可以通过 Web 组件的互操作性将不同部分结合起来,这种可组合性在大型企业、大规模项目和独立团队中都很常见,同时也作为迁移大型或老旧代码库的有效路径。
我目前在 eBay 的工作中积极应用 Web 组件。对我来说,Web 组件已成为不可或缺的重要工具。我正在将一个基于 PHP、jQuery 和 Sass 的老旧网站逐步迁移到 Svelte 上。在实现这个目标时,Web 组件发挥了关键作用,特别是轻量 DOM 渲染功能,这对满足各种需求(尤其是兼容 slot 和 Vite HMR)至关重要。为了弥合新旧代码之间的差距,我开发了一个名为 svelte-retag 的 NPM 包,它是 svelte-tag 的分支,用于简化过渡过程,避免立即进行大规模重写。这也为将来升级至 SvelteKit(与基于 PHP 的无头 CMS 配合使用)提供了重要基础。
Web 组件最引人注目的特性之一是其能够作为通用语言来规范化和序列化组件语言。这一点在 PHP 开发中非常有用,可以根据需要在任何位置生成和提交组件,然后轻松在客户端进行渲染。此外,Web 组件扩展了 SSR 的潜力,尤其是在使用轻量 DOM 时,甚至可以支持重度使用 PHP 的混合环境。虽然我尚未具体尝试,但我建议大家考虑尝试 enhance-ssr 和 webc,它们似乎为 Web 组件的 SSR 提供了更多选项,其中 webc 还提到了与浏览器原生 Shadow DOM 样式范围相关的特性。
另一个典型用例是在公司的主网站和招聘网站之间高效无缝地应用统一标头。这并不是依赖于 Svelte,而是一种封装方法,允许我们在基于 jQuery 和 Sass 的旧有布局和功能上发布基于 SSR 的标头,同时始终保持 Shadow DOM 的安全性,不受主页以外的外部干扰(反之亦然,毕竟我们不想在对方的代码中引入回归问题)。
尽管现在已经是 2023 年,但使用 Web 组件仍可能引入额外的复杂性,特别是在处理 SSR 和可访问性问题时。任何不当处理都可能对最终用户产生明显的影响,因此需要仔细权衡是否值得采用。
因此,考虑到这些因素,我目前仍然坚持使用轻量 DOM,因为它对我们来说带来了实际优势,特别是在支持旧代码方面。
作者回复:非常有道理!我认为实际上有大约 80%的所谓“Web 组件问题”其实都可以追溯到“Shadow DOM 问题”,因此,去除 Shadow DOM 可以避免许多可能的麻烦。然而,正如你提到的,轻量 DOM 中的“slots”也是一个重要特性,所以在放弃 Shadow DOM 的同时,我们也失去了<slot>,这确实令人遗憾。
参考链接:
https://daverupert.com/2023/07/why-not-webcomponents/
https://nolanlawson.com/2023/08/23/use-web-components-for-what-theyre-good-at/
评论