最近我收到了一系列关于 Web 组件 的新闻,所以我想在这里整理一下。
在我看来,Web 组件的最佳用例之一是模式库。例如,与其像在 Bootstrap 中那样使用 <ul class="nav nav-tabs">
或像在 Bulma 中那样使用 <div class="tabs">
,你可以使用自定义元素,例如 <designsystem-tabs>
。
新的 Shoelace 库 使用 sl
命名空间来定义组件。它是一个完全基于 Web 组件的模式库。因此,那里的选项卡 是 <sl-tab-group>
元素。
为什么这很好?首先,它引入了组件模型。这意味着,如果你正在处理一个组件,它拥有 一个模板和一个样式表,它们位于同一位置。查看 Shoelace 的内部实现,你可以看到这一切都是基于 Stencil 的。
另一个好处是,它意味着组件可以使用(并且确实使用了)Shadow DOM。这提供了一种来自 Web 平台本身的隔离形式。对于我们这些 CSS 开发者来说,这意味着选项卡组件中选项卡的样式是使用 .tab
类(嘿,哇,太酷了)完成的,但它在该组件中是隔离的。即使使用如此通用的名称,我也不会意外地干扰页面上使用该通用类的其他组件,也不会有一些外部 CSS 干扰这里的内部样式。Shadow DOM 就像一道安全墙,防止样式泄漏或渗入。
我刚刚看到了 FAST 框架¹,它也是一组组件。它定义的选项卡为 <fast-tabs>
。这让我想起了关于 Web 组件作为模式库方法的另一个优点:它感觉像是由 API 驱动的,甚至从组件本身的名称开始,这正是你在 HTML 中使用的内容。该元素上的属性可以完全自定义。似乎新兴的标准是,你甚至不需要为控制组件的自定义属性添加 data-*
前缀。因此,如果我要创建一个选项卡组件,它可能是 <chris-tabs active-tab="lunch" variation="rounded">
。
也许使用 Web 组件作为模式库的最大参与者是 Ionic。 他们的选项卡 为 <ion-tabs>
,你可以使用它们而无需涉及任何其他框架(尽管除了他们自己的 Stencil 之外,它们还支持 Angular、React 和 Vue)。Ionic 在 Web 组件方面取得了长足的进步,最近 支持 Shadow Parts。以下是 Brandy Carney 对封装的再次解释
Shadow DOM 有助于防止样式从组件中泄漏并意外应用于其他元素。例如,我们将
.button
类分配给我们的ion-button
组件。如果 Ionic Framework 用户在其自己的元素之一上设置.button
类,它将在框架的早期版本中继承 Ionic 按钮样式。由于ion-button
现在是一个 Shadow Web 组件,因此这不再是一个问题。但是,由于这种封装,样式也无法渗透到 Shadow 组件的内部元素。这意味着,如果 Shadow 组件在其 Shadow 树内部渲染元素,用户将无法使用他们的 CSS 来定位内部元素。
封装是一件好事,但它确实使得样式变得“更难”(有意为之)。有一个重要的 CSS 概念需要了解:**CSS 自定义属性可以穿透 Shadow DOM**。但是,人们决定——我认为是正确的——在设计系统中将所有东西都“变量化”并不是一种明智的途径。相反,他们为 Shadow DOM 内部的每个 HTML 片段分配一个部分,例如 <div part="icon">
,这使我们能够使用 CSS “从外部访问”它,例如 custom-component::part(icon) { }
。
我认为基于部分的样式挂钩总体上还不错,并且是此类模式库的明智发展方向,但我承认其中一部分让我感到困扰。选择器的工作方式与预期不符。例如,你无法有条件地选择事物。你也不能选择子元素或使用级联。换句话说,它只是一个一次性的操作,或者就像你用手直接穿过一层膜。你可以伸出手去抓取某个东西,或者不抓,但你完全无法做其他任何事情。
说到让人生气的事情,Andrea Giammarchi 有一个很好的观点关于 Web 组件的现状
每个开始的库,包括我的库,都建议我们应该导入库来定义原本应该是一个“可移植的自定义元素”的东西。
Google 总是建议使用 LitElement。Microsoft 想要你使用 FASTElement。Stencil 有自己的 组件。hyperHTML 有自己的 组件。**没有人只使用“原始”的 Web 组件。**这很奇怪!我认为最糟糕的部分是 Web 组件应该是一个“原生平台”的东西,这意味着我们不应该需要依赖某种特定技术才能使用它们。当我们这样做时,我们就像被绑定到它一样,就像我们仅仅使用 React 或其他任何东西一样。
Andrea 在 那篇文章中提出了一些想法,包括使用 一些新的、更小的库。我认为我想看到的是一个根本不使用任何库的模式库。
- FAST 在首页上连续两句话中将自己称为“界面系统”,然后又称为“UI 框架”。Shoelaces 将自己称为“库”,但我称之为“模式库”。我发现“设计系统”是描述这个概念最常用的术语,但通常比特定技术使用得更广泛。FAST 在代码本身中使用该术语来表示控制主题的包装器元素。我想说围绕所有这些东西的术语远未确定。
你检查过 https://haxtheweb.org 了吗?
而且像 BYU 这样的地方正在使用原生 WC >> http://webcomponents.byu.edu
这个 Fast 框架太棒了!绝对值得一试……他们网站做得非常棒……它是微软开发的吗?它与 Fluent UI 有什么区别?
是的,FAST 由微软的一个团队与 Edge 合作构建和维护(新版 Edge 浏览器的 UI 大部分都是使用 FAST 的一个版本构建的)。也就是说,FAST 的主要目标是帮助任何开发者加速采用现代 Web 平台功能,例如 Web 组件。为此,FAST 没有绑定到特定的设计系统或框架,因此它可以尽可能多地帮助开发者。有关 FAST 和 Fluent UI 如何关联的更多信息很快就会发布,但我认为你会喜欢它(不用说,我们是合作伙伴)。
PatternFly Elements,Red Hat 的开源设计系统,是一个原生 Web 组件系统。我们使用自定义属性进行主题设置,以及一种轻量级 DOM 作为数据的方法,这意味着组件可以优雅地升级。
哇,shadow-parts 听起来很棒!
哎呀,我可能不得不再次探索 Web 组件了。
他们解决如何在组件之间执行 CSS 重置了吗?我想我导入了一个特定的重置文件,这很可能是不好的!
使用像 LitElement 这样的库绝不像是使用 React。它除了 Web 标准外,对组件的用户没有任何强加。LitElement 在渲染时提供了性能改进,以及在开发时提供了语法糖。
Web 组件可能有其用例,但我讨厌在 UI 框架中看到它们。它们违背了 Web 设计!它们难以调试和设置样式,并且使用起来很麻烦。它们是对昨日问题的解决方案,是对已经过度设计的堆栈的不必要复杂化。
不错的总结,谢谢!
关于“没有人使用原始 Web 组件”的部分:据我了解,规范旨在作为“低级” API,旨在与构建在其上的某种抽象一起使用。当编写“原始”Web 组件时,你通常会编写大量样板代码。这就是为什么我选择像 LitElement 这样的抽象。
确实,你购买了某种供应商锁定。但这仅限于特定组件。你完全可以在 Stencil 中编写你的选项卡,在 LitElement 中编写按钮,同时在旁边使用一些 IonSomethings。
当然,你可以在网站上构建一些 Vue 小部件,在 React 中构建一些其他小部件,但随后你会获得这两个运行时。当你只使用 Web 组件作为你的组件时,大多数库/框架都不会附带大型运行时。
总而言之,对我来说,开发者体验的优势大于一点锁定带来的劣势。虽然框架开销很小,所以不会影响用户体验。
你不需要!你可以在同一个页面上使用
<fast-tabs>
和<sl-tabs>
,没问题。Chris,关于 Web Components 社区的更新很棒。我喜欢你对不使用库的组件的想法,并且想知道你是否见过来自 Twitter 上 @passle_ 的https://modest-bhaskara-e8742f.netlify.app/。它不仅避免了 JS 库依赖,而且旨在避免 CSS 库依赖,并使其非常容易“自带样式”。
我希望你能查看一下,但我对你在此处分享的想法也很感兴趣,即使用库最终会让我们“就像我们只使用 React 或其他东西一样被锁定”。如果我选择
<FrameworkFancytTabs/>
在我的应用程序中运行,我需要找到一种方法在我的大型应用程序中运行该框架(也许它已经存在,也许不存在)。而且,由于基于框架的组件是添加到框架中的依赖项,并且该框架强制执行对您选择利用的组件的父级始终存在,因此我们存在锁定。但是,在处理你在此处列出的任何自定义元素时,例如<fancy-tabs></fancy-tabs>
,用于构建它的库是该元素的依赖项,因此,当你采用或放弃该元素时,库依赖项也是如此。当然,也有一些自定义元素会公开一个与框架一样绑定到该库的 API,但这只是 API 设计的结果,可以并且应该被视为代码异味,而不是选择依赖库会强制锁定。我确实同意,在组件内部,也就是作为组件开发人员,我们选择的库可以并且确实会导致某种形式的锁定,尽管幸运的是它的规模较小,并且,正如你在本文中没有提到的那样,它是封装的。这样,我确实希望平台能够达成一个更强大的类似渲染器的 API。你是否花时间研究过https://docs.google.com/document/d/1UVtF1gM6XY-_NKm2_c045K803U3ZTjfyluLeToJlIUk/preview或https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md这两个非常有趣的提案,它们将开始为浏览器带来这种能力?
这是一篇很棒的文章,但我确实想强调一些事情。
“Web Components 应该是一种‘原生平台’的东西,这意味着我们不应该需要购买某种特定技术才能使用它们。”
Web Components 被设计为可扩展 Web 运动的一部分,并且不打算取代库/框架。其想法是创建库可以构建在其之上的基本元素,以便它们可以交付更少的代码。一直以来的意图都是你将与它们一起使用库。
理论上,这不会造成锁定,因为组件的接口是标准的 DOM API(属性、特性、事件)。这意味着你可以将 shoelace 组件与 ion 组件一起使用,即使它们是用不同的库构建的,它们仍然可以通信和共存。
为库构建低级基本元素的策略最终是否会成功还有待观察。也许 Web Components 应该有更花哨的抽象,这样你就不需要库了(我真不知道),但它们没有这样做主要原因是,当时所有设计 API 的人都知道我们很有可能设计出错误的抽象。我们只是没有看到足够的人构建自定义元素来了解他们需要哪些额外的细节。
例如,曾尝试标准化数据绑定(该提案称为模型驱动视图或 MDV),它基于 Angular.js 的双向绑定。该提案被放弃了,而且,世界也从双向绑定转向了更多单向数据流模式(React props/Redux)。
现在几年过去了,我认为有些事情变得越来越清晰。也许如果一个基于 Web Components 的库取得了巨大的成功(比如,jQuery 级别的成功),那将向我们展示“道路”,并且这些抽象可以被标准化。
我认为巨大的区别在于你可以组合多个 Web Components。
你想在同一个页面上使用
ion-tabs
和fast-tabs
?当然没问题,尽管使用。所以也许没有很多人直接使用
class MyElement extends HTMLElement
,但这并不重要,只要基类是HTMLElement
的“扩展”。而对于所有这些来说都是如此——LitElement、Ionic、Fast 等。你可以把它看作是 HTMLElement 上的一些糖。它帮助我以我喜欢的写法来编写 Web Component。每个人都可以选择自己的糖。没有恶意。
PS:这与框架有很大区别……因为在 React 应用程序中使用 Angular tabs 实现可能是不建议的
Chris,很棒的文章。我们创建了 Stencil(我是 Ionic 的 CEO),所以对以上内容有一些想法。
关于:Shadow DOM:我同意,在易于样式方面它并不理想,但我们对 Shadow Parts 非常兴奋。Shadow DOM 讨论中缺少的一件事是,它使你能够围绕样式创建“公共 API”,从而使用户更容易迁移到新版本的组件而不会出现重大更改。例如,作为过去 Bootstrap 的用户,我亲眼看到了基于类选择器且没有任何强制隔离的样式系统存在多么大的问题,因为你经常会陷入特定版本。
至于关于锁定的最后一段,我认为与 React 或 Angular 等框架相比,使用像 Stencil 这样的 Web Components 库/编译器时的锁定语义从根本上不同,尤其是在消费者方面(即采用 Web Components 构建库的用户)。
是的,Web Components 的*作者*必须选择使用这些库,但消费者可以自由选择他们想要的任何框架或库。这是 Web Components 和经典框架组件方法之间的根本区别。Stencil 通过自动为 React、Angular、Vue(以及任何未来的流行框架)生成原生绑定,进一步迈进了一步,因此这些框架的使用者可以以框架原生方式使用这些组件。
根据我们的经验,转向 Web Components 目标具有变革意义。我们看到输出目标的稳定性很强(因为 WC 是标准化的),但现在更广泛的 Web 世界可以利用我们的组件。我们希望其他人尝试这种方法,并看到好处。
非常感谢你为 Stencil 做出的贡献 Max!我围绕它建立了我的第一个成功的业务:https://spx.dev,并专门用它来开发我的最终学士学位项目的一个应用程序。我当然以满分通过了考试。:-)
Stencil 获胜!
是的,正如 Max 所说,自定义元素的最终用户可以在任何框架中使用该元素,而不管元素作者选择使用哪个自定义元素库。最终用户可以获取该元素并将其粘贴到 React 应用程序、Vue 应用程序、Angular 应用程序、Svelte 应用程序或旧的 jQuery 应用程序中。
最终用户使用自定义元素的方式具有很高的互操作性,并且占用空间比非自定义元素库和框架小(参见 Rob Eisenberg 的评论),因此即使使用特定的库,这也是一个双赢的局面。
扩展 Rob Dodson 的评论,当前的自定义元素提供了一组我们能够可靠地依赖且知道不会消失的低级基本 API(这是希望)。然后,随着时间的推移,可能会发生的事情是,自定义元素库经常重新创建的最常见和最有用的模式可以帮助指导 Web API 的下一个附加组件,并且新的 API 将允许自定义元素作者在使用更少库的同时实现更多功能。
正在讨论诸如声明式自定义元素之类的功能
https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Declarative-Custom-Elements-Strawman.md(讨论也发生在该存储库的问题标签中)
或
<template>
元素的数据绑定(例如,想象使用 Handlebars 或某种语法来对 DOM 进行反应式和声明式更新)。查看示例并加入对话https://discourse.wicg.io/t/extension-of-template/447
一旦库确定哪些模式很棒哪些不怎么样,那么就可以更有信心地将最佳模式添加到 Web 中。
检查哪些模式在性能方面胜出的一个好地方是 Stefan Krause 的 JS 框架基准测试
https://krausest.github.io/js-framework-benchmark/current.html
那里最快的框架的共同点是它们使用细粒度的更新机制,而不是虚拟 DOM 差异。你会看到各种比 React、Vue 或 Angular 更快的全新库,同时仍然提供良好的开发者体验。
库仍在突破界限:如果 Web 仅采用 vdom 方法,而该概念却失去了吸引力,那对 Web 来说将是一件坏事(例如)。
特别是,来自该基准测试的 Solid.js 非常简洁快速。我采用了它并在其之上构建了一个自定义元素系统,现在它@lume/element 是最简单和最快的自定义元素系统之一
https://github.com/lume/element
我非常乐意接受任何反馈!
总而言之,是的,库使创建自定义元素变得更容易,并且自定义元素作者可能希望选择一个,但最终用户获得的互操作性非常强大,可以防止最终用户被锁定到任何特定框架。将来,即使没有任何库,自定义元素也可能更容易编写,你可以通过加入上面 GitHub 或 WICG 论坛中的讨论来帮助实现这一点。
很棒的文章,但在脚注中有一个小错误:FAST 网站称自己为“接口系统”,并在下一段中说它“可以与任何现代 UI 框架(Angular、React、Vue 等)一起使用”,而不是说它是一个 UI 框架。
另外,我可能误解了,但值得指出的是,您不需要使用 FAST Element 才能使用 FAST 组件。如果您要构建自己的组件,则可能需要使用 FAST Element。也就是说,您可以使用 FAST 组件,然后使用原生 Web 组件代码或 Lit 或者任何您想要的方式将它们组合起来。FAST 不使用“纯粹”的 Web 组件的主要原因是,仍然需要大量的样板代码和冗余代码,因此常见的功能被整合到 FAST Element 库中。也就是说,我同意如果平台只是提供这些功能,那么我们就根本不需要 FAST Element 会很棒。
当我说到 Web 组件时,我提醒大家的一件事是,我们在浏览器中拥有的内容最初被设计为一组非常低级且不带任何意见的 API。目标是让库和框架作者能够在这些基本元素之上创建带意见的便利层。这正是 FAST、Lit、Stencil 等所做的事情。
需要注意的是,这与 JavaScript 框架方法不同。Web 组件库默认情况下是可互操作的。如果您从 FAST 开始,然后发现一个使用 Lit 创建的酷炫组件,您可以将它们一起使用。这方面没有问题。而且由于我们都在构建 Web 标准,因此我们实现的代码更少,从而导致库更小。事实上,您甚至可以一起使用 2-3 个 Web 组件库,最终得到的结果仍然比许多前端框架更小。换个角度思考,JQuery 的压缩和 gzip 后的尺寸约为 29kb。您可以将 FASTElement 和 LitElement 都放入相同的空间中,并且还有很多剩余的空间,也许甚至可以容纳第三个 WC 库。我们在实现 FAST 时还发现了另一件事。我们的整个 FASTElement 库,加上我们的自适应设计系统层,加上我们所有 v1 核心组件,大约与 React+React.DOM 的大小相同。当您考虑使用 Web 组件获得的每 KB 的价值时,它通常远高于非 Web 组件选项。
过去几年,JavaScript 框架领域发生了很多变化。借助 Web Components,我们正在共同努力创建一个更加开放和互操作的解决方案,使设计师和开发人员能够创建更好的应用程序,而无需一遍又一遍地支付 JS 框架的成本。
来自 htmlelements.com 和 vaadin 的 Web 组件可能值得在未来的类似文章中提及。他们在自定义元素 v1 规范发布之前就构建了 Web 组件。
这是我尝试创建命名空间注册表的示例
https://github.com/nuxodin/web-namespace-registry