Web Components

一.What?


Web Components are a new browser feature that provides a standard component model for the Web, consisting of several pieces: Shadow DOM, Custom Elements, HTML Imports and HTML Templates.

(摘自w3c/webcomponents)

也就是说,Web Components是Web组件模型标准,由浏览器提供原生特性支持,包括Shadow DOM,Custom Elements,HTML Imports和HTML Templates

规范状态
依赖的4个东西,目前(2017/12/1)状态如下:


Completed Work
2014-03-18  HTML Templates  Group Note

Drafts
2017-09-05  Shadow DOM  Working Draft
2016-10-13  Custom Elements  Working Draft
2016-02-25  HTML Imports  Working Draft

Obsolete
2014-07-24  Introduction to Web Components  Retired

(摘自All Standards and Drafts – W3C)

那么,这4个依赖项现在只完成了1个(HTML Templates),而且还没能成为推荐标准(只是NOTE而没能成为REC)。其余3个都还处于工作草案(WD)状态

P.S.关于REC,NOTE等W3C文档状态的更多信息,请查看W3C规范制定流程

而Web Components规范自身在14年出过一版(Introduction to Web Components),被废弃掉了(Retired),目前Web Components处于无规范状态,github好像还有动静

P.S.实在好奇的话,可以看一眼被废弃的版本

实现状态
虽然规范尚处于不明朗的状态,但部分浏览器对Web Components依赖的特性已经提供了不同程度的支持:


Shadow DOM
    v0 Chrome25+ Firefox X Safari X Opera15+ Android4.4+ IOS Safari X
    v1 Chrome53+ Firefox X Safari 10+ Opera40+ Android5+ IOS Safari 10.2+
Custom Elements
    v0 Chrome33+ Firefox X Safari X Opera20+ Android4.4.4+ IOS Safari X
    v1 Chrome54+ Firefox X Safari 10.1+ Opera41+ Android5+ IOS Safari 10.3
HTML Imports
    Chrome36+ Firefox X Safari X Opera23+ Android5+ IOS Safari X
HTML Templates
    Chrome26+ Firefox 22 Safari 7.1 Opera15+ Android4.4+ IOS Safari 8

注意:Android5+指的是Android5-6.x WebView: Chromium 62,而且未区分部分支持与完整支持
Chrome,Opera一路绿到底,Safari及移动端差不多跟得上脚步,Firefox打打酱油。但从目前的兼容形势来看,PC端和移动端都不可用

P.S.关于兼容性的更多信息,请查看Can I use… Support tables for HTML5, CSS3, etc

此外,还有polyfill和组件库可以试玩:

Polymer 2.0:Google

X-Tag:Microsoft

作用
Web Components希望提供规范的组件定义方式,推进Web组件标准化

由浏览器来支持组件化方案需要的环境特性,包括Shadow DOM,Custom Elements,HTML Imports和Templates

那么先看看这几个依赖特性能做什么:

Shadow DOM:沙箱环境,用于组件隔离。组件不受外部影响,组件间也互不影响

Custom Elements:组件引用方式。以自定义元素的形式引用组件

HTML Imports和Templates:组件资源加载方式与组件声明方式。组件声明放在<template>标签里,通过<link rel="import" href>加载组件资源

我们发现核心是组件封装,通过Shadow DOM把组件细节隐藏起来,效果类似于:


<video src="./video.mp4" controls></video>

含有该片段的HTML页面将呈现一个功能完整的视频播放器,带播放按钮,进度条,音量调节按钮等等

Web Components的用法与之类似:


<my-app>
    <my-nav-bar>
        <my-title>Order Center</my-title>
    </my-nav-bar>
    <my-aside-menu/>
    <my-filter-panel/>
    <my-order-list/>
</my-app>

把视图结构和样式包进自定义HTML元素里,以黑盒组件形式引用。组件定义在隔离的环境中(Shadow DOM),HTML,CSS,Script都是安全的,外部无法直接改变组件内部的逻辑/视图状态

当然,组件除了封装性,至少还欠缺:

组合方式,比如通过Shadow DOM的<slot>

通信机制,比如通过attributeChangedCallback等生命周期Hook

这部分内容应该定义在Web Components自身规范里(比如之前被废弃掉的那个Introduction),至于更上层的东西,则不在Web Components的考虑范围内,毕竟组件标准化的第一步应该是从无到有

二.从video说起
只要写一行video标签:


<video src="./foo.webm" controls></video>

页面就能呈现出功能完整的视频播放器,那播放按钮,进度条的结构定义和样式声明都藏在哪里呢?难道是像单选按钮等表单元素一样,由系统平台渲染控件?这样的话,那平台无关的部分呢,比如文本框的placeholder,是怎么实现的?

实际上,文本框的placeholder与video类似,一些能看到但(在结构化文档里)找不到的元素都藏在Shadow DOM里:

Web Components

video、input相当于浏览器的内置组件,组件视图结构及默认样式藏在Shadow DOM里,组件逻辑被彻底藏了起来,仅暴露出autoplay,oninput等状态/行为Hook与外界通信

到这里,我们发现与Web Components的概念非常相像,所谓Web Components无非是把浏览器的组件机制暴露出来,给Web开发者用,这样我们也能愉快地定义“原生控件”(组件)了

三.Shadow DOM
前面一直强调把东*在Shadow DOM里,因为Shadow DOM的作用相当于sandbox(沙箱),提供组件隔离环境

可以在Chrome试玩这个特性:

Web Components

打开Shadow DOM显示开关之后,我们瞅一眼文本框的隐藏部分:


<input class="nav-search column-07 start-18" name="s" type="text" placeholder="嗯。我看到过你的小熊。">
  #shadow-root (user-agent)
    <div pseudo="-webkit-input-placeholder" id="placeholder" style="display: block !important; text-overflow: clip;">嗯。我看到过你的小熊。</div>
    <div id="inner-editor"></div>

placeholder静静地待在这里,#inner-editor用来显示输入的文本

利用浏览器提供的Shadow DOM特性,我们可以创建自己的Shadow Root:


document.body.innerHTML = '<div class="container"></div>';
var host = document.querySelector('.container');
var root = host.createShadowRoot();
root.innerHTML = '<p>How <em>you</em> doin?</p>'

此时节点结构是这样:


<div class="container"></div>
  #shadow-root (open)
    <p>How <em>you</em> doin?</p>

页面显示“How you doin?”,因为Shadow Root下的内容会在页面呈现出来

Shadow Root是指createShadowRoot()返回的Fragment:

host.createShadowRoot() instanceof DocumentFragment === true
可以对Fragment做DOM操作,相当于一个独立的HTML解析环境,不受外界干扰

另外还有2个概念:Shadow Host与Shadow Boundary

Shadow Host
Shadow Root的“宿主”,Shadow DOM与DOM的连接点,说白了就是Shadow DOM挂在哪里(上例中的host)

既然有寄宿关系,那么我们试一下:


host.parentElement.removeChild(host)

发现body里什么都没有了,Shadow DOM随着宿主一起被干掉了(返回值是游离的div节点,此时Shadow DOM仍然挂在div身上,可以把节点在append回来验证一下)

Shadow Boundary
一个抽象概念,指的是Shadow DOM外面的这层“结界”,它能够把Shadow Root下的HTML和CSS隔离起来,与Shadow Host所在的文档里的样式互不影响,且外界无法通过JS获取Shadow Root下的节点对象,类似于iframe的隔离效果

这正是Web开发一直想要的模块隔离,虽然可以通过命名空间等工程化方案填补,但总有一些无法弥补的缺陷,根本原因是最终呈现在页面上的HTML与CSS没有作用域的概念,开发阶段中的限制手段到这里都成了道德约束。另一方面,以工程化手段去约束,还可能存在限制太多导致业务束手束脚的问题

Insertion Points
另外,还有必要知道组件组合的基本支持:


<div class="breaking">
    <ul>
        <slot name="breaking"></slot> <!-- slot for breaking news -->
    </ul>
</div>
<div class="other">
    <ul>
        <slot></slot> <!-- slot for the rest of the news -->
    </ul>
</div>

(摘自Shadow DOM W3C Editor’s Draft 14 November 2017)

两种插入方式,分别表示具名槽和默认槽,看着很眼熟,Vue模版就这么写,因为:


For example, Vue components implement the Slot API and the is special attribute.

至于Vue与Web Components的关系,我们后面再说

四.Custom Elements
创建自定义元素,锦上添花的小东西。没有自定义元素特性的话,我们需要这样做:


<ul class="stories">
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/3">Also a story</a></li>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/5">Awesome story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/6">Horrible story</a></li>
</ul>

有了的话,就这样写:


<stories>
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <breaking slot="breaking"><a href="//example.com/stories/3">Also a story</a></breaking>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/5">Awesome story</a></li>
    <breaking slot="breaking"><a href="//example.com/stories/6">Horrible story</a></breaking>
</stories>

从语义的角度看,自定义元素的表达力更强一些,也更简洁

自定义标签有2个约束:

标签名必须带有短线

原型必须继承自HTMLElement

同样可以试玩:


document.body.innerHTML = '<template id="tmpl"><p>How <em>you</em> doin?</p><style>em {font-size: 200%;}</style></template><div class="container"></div>';

// Grab our template full of markup and styles
var tmpl = document.querySelector('#tmpl');

// Create a prototype for a new element that extends HTMLElement
var HowYouDoinProto = Object.create(HTMLElement.prototype);

// Setup our Shadow DOM and clone the template
HowYouDoinProto.createdCallback = function() {
var root = this.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
};

// Register our new element
var HowYouDoin = document.registerElement('how-you-doin', {
prototype: HowYouDoinProto
});

document.querySelector('.container').innerHTML = '<how-you-doin/>';

能够得到:


<div class="container">
    <how-you-doin>
      #shadow-root (open)
        <p>How <em>you</em> doin?</p>
        <style>em {font-size: 200%;}</style>
    </how-you-doin>
</div>

呈现的内容也如我们所愿:一个大号的you

上例中的document.registerElement即Custom Elements特性。至于importNode(),则只是一个普通的DOM API(不是Custom Elements的一部分),用来克隆节点,否则模版就是一次性的

至于HTML Imports,就更不重要了,同样有CORS,简单的组件加载方案,与ajax手动加载组件没太大区别。在HTTP2.0时代真正到来之前,生产环境还是不要分多文件了

P.S.对HTML Imports感兴趣的话,可以查看在线Demo与参考资料

五.Vue与Web Components
到现在为止,上面提到的所有例子,怎么看怎么像Vue组件定义,没错,因为Vue在实现上遵从了部分Web Components规范,比如Shadow DOM里的slot:


You may have noticed that Vue components are very similar to Custom Elements, which are part of the Web Components Spec. That’s because Vue’s component syntax is loosely modeled after the spec. For example, Vue components implement the Slot API and the is special attribute.

主要有2点区别:


The Web Components Spec is still in draft status, and is not natively implemented in every browser. In comparison, Vue components don’t require any polyfills and work consistently in all supported browsers (IE9 and above). When needed, Vue components can also be wrapped inside a native custom element.

Vue components provide important features that are not available in plain custom elements, most notably cross-component data flow, custom event communication and build tool integrations.

因为Web Components规范尚不成熟,且支持性并不乐观,不用polyfill就无法投入生产,Vue依靠构建工具跨过了环境兼容性问题,不依赖浏览器特性支持,但同时也就舍弃了Shadow DOM封装性等Web Components核心优势

另外,Web Components是相对底层的组件规范,Vue除了定义组件规范,还提供了组件通信,数据绑定等上层方案

六.在线Demo
地址:http://www.ayqy.net/temp/web-components/image-slider.html

P.S.一个实现很巧妙的纯CSS带指示器的image slider组件,很有意思,刷新了笔者对一般相邻选择器(E ~ F)的看法

参考资料
A Guide to Web Components:很不错的Web Components指南

WebComponents/ – W3C Wiki

Exploring HTML Imports

Shadow DOM W3C Editor’s Draft 14 November 2017:Shadow DOM编辑草稿

Vue.js – Relation to Custom Elements

上一篇:用户和用户组管理


下一篇:2021.3.6 css书写规范