深度介紹:💾 你聽說過原生 HTML 組件嗎?

嘿!看看這幾年啊,Web 前端的發展可是真快啊!

想想幾年前,HTML 是前端開發者的基本技能,通過各式各樣的標籤就可以搭建一個可用的網站,基本交互也不是問題。如果再來點 CSS,嗯,金黃酥脆,美味可口。這時候再撒上幾把 JavaScript,簡直讓人欲罷不能。

隨着需求的增長,HTML 的結構越來越複雜,大量重複的代碼使得頁面改動起來異常困難,這也就孵化了一批批模版工具,將公共的部分抽取出來變爲公共組件。再後來,隨着 JavaScript 的性能提升,JavaScript 的地位越來越高,不再只是配菜了,前端渲染的出現降低了服務端解析模版的壓力,服務端只要提供靜態文件和 API 接口就行了嘛。再然後,前端渲染工具又被搬回了服務端,後端渲染出現了(黑人問號???)

總之,組件化使得複雜的前端結構變得清晰,各個部分獨立起來,高內聚低耦合,使得維護成本大大降低。

那麼,你有聽說過原生 HTML 組件嗎?

四大 Web 組件標準

在說原生 HTML 組件之前,要先簡單介紹一下四大 Web 組件標準,四大 Web 組件標準分別爲:HTML Template、Shadow DOM、Custom Elements 和 HTML Imports。實際上其中一個已經被廢棄了,所以變成“三大”了。

HTML Template 相信很多人都有所耳聞,簡單的講也就是 HTML5 中的 <template> 標籤,正常情況下它無色無味,感知不到它的存在,甚至它下面的 img 都不會被下載,script 都不會被執行。<template> 就如它的名字一樣,它只是一個模版,只有到你用到它時,它纔會變得有意義。

Shadow DOM 則是原生組件封裝的基本工具,它可以實現組件與組件之間的獨立性。

Custom Elements 是用來包裝原生組件的容器,通過它,你就只需要寫一個標籤,就能得到一個完整的組件。

HTML Imports 則是 HTML 中類似於 ES6 Module 的一個東西,你可以直接 import 另一個 html 文件,然後使用其中的 DOM 節點。但是,由於 HTML Imports 和 ES6 Module 實在是太像了,並且除了 Chrome 以外沒有瀏覽器願意實現它,所以它已經被廢棄並不推薦使用了。未來會使用 ES6 Module 來取代它,但是現在貌似還沒有取代的方案,在新版的 Chrome 中這個功能已經被刪除了,並且在使用的時候會在 Console 中給出警告。警告中說使用 ES Modules 來取代,但是我測試在 Chrome 71 中 ES Module 會強制檢測文件的 MIME 類型必須爲 JavaScript 類型,應該是暫時還沒有實現支持。

Deprecated HTML Imports

Shadow DOM

要說原生 HTML 組件,就要先聊聊 Shadow DOM 到底是個什麼東西。

大家對 DOM 都很熟悉了,在 HTML 中作爲一個最基礎的骨架而存在,它是一個樹結構,樹上的每一個節點都是 HTML 中的一部分。DOM 作爲一棵樹,它擁有着上下級的層級關係,我們通常使用“父節點”、“子節點”、“兄弟節點”等來進行描述(當然有人覺得這些稱謂強調性別,所以也創造了一些性別無關的稱謂)。子節點在一定程度上會繼承父節點的一些東西,也會因兄弟節點而產生一定的影響,比較明顯的是在應用 CSS Style 的時候,子節點會從父節點那裏繼承一些樣式。

而 Shadow DOM,也是 DOM 的一種,所以它也是一顆樹,只不過它是長在 DOM 樹上的一棵特殊的紫薯🍠,啊不,子樹。

什麼?DOM 本身不就是由一棵一棵的子樹組成的嗎?這個 Shadow DOM 有什麼特別的嗎?

Shadow DOM 的特別之處就在於它致力於創建一個相對獨立的一個空間,雖然也是長在 DOM 樹上的,但是它的環境卻是與外界隔離的,當然這個隔離是相對的,在這個隔離空間中,你可以選擇性地從 DOM 樹上的父節點繼承一些屬性,甚至是繼承一棵 DOM 樹進來。

利用 Shadow DOM 的隔離性,我們就可以創造原生的 HTML 組件了。

實際上,瀏覽器已經通過 Shadow DOM 實現了一些組件了,只是我們使用過卻沒有察覺而已,這也是 Shadow DOM 封裝的組件的魅力所在:你只管寫一個 HTML 標籤,其他的交給我。(是不是有點像 React 的 JSX 啊?)

我們來看一看瀏覽器利用 Shadow DOM 實現的一個示例吧,那就是 video 標籤:

<video controls src="./video.mp4" width="400" height="300"></video>

我們來看一下瀏覽器渲染的結果:

Video Tag

等一下!不是說 Shadow DOM 嗎?這和普通 DOM 有啥區別???

在 Chrome 中,Elements 默認是不顯示內部實現的 Shadow DOM 節點的,需要在設置中啓用:

Turn on Shadow DOM 1
Turn on Shadow DOM 2

注:瀏覽器默認隱藏自身的 Shadow DOM 實現,但如果是用戶通過腳本創造的 Shadow DOM,是不會被隱藏的。

然後,我們就可以看到 video 標籤的真面目了:

True Face of Video Tag

在這裏,你可完全像調試普通 DOM 一樣隨意調整 Shadow DOM 中的內容(反正和普通 DOM 一樣,刷新一下就恢復了)。

我們可以看到上面這些 shadow DOM 中的節點大多都有 pseudo 屬性,根據這個屬性,你就可以在外面編寫 CSS 樣式來控制對應的節點樣式了。比如,將上面這個 pseudo="-webkit-media-controls-overlay-play-button" 的 input 按鈕的背景色改爲橙色:

video::-webkit-media-controls-overlay-play-button {
  background-color: orange;
}

Change Button Colour in Video Tag

由於 Shadow DOM 實際上也是 DOM 的一種,所以在 Shadow DOM 中還可以繼續嵌套 Shadow DOM,就像上面那樣。

瀏覽器中還有很多 Element 都使用了 Shadow DOM 的形式進行封裝,比如 <input><select><audio> 等,這裏就不一一展示了。

由於 Shadow DOM 的隔離性,所以即便是你在外面寫了個樣式:div { background-color: red !important; },Shadow DOM 內部的 div 也不會受到任何影響。

也就是說,寫樣式的時候,該用 id 的時候就用 id,該用 class 的時候就用 class,一個按鈕的 class 應該寫成 .button 就寫成 .button。完全不用考慮當前組件中的 id、class 可能會與其他組件衝突,你只要確保一個組件內部不衝突就好——這很容易做到。

這解決了現在絕大多數的組件化框架都面臨的問題:Element 的 class(className) 到底怎麼寫?用前綴命名空間的形式會導致 class 名太長,像這樣:.header-nav-list-sublist-button-icon;而使用一些 CSS-in-JS 工具,可以創造一些唯一的 class 名稱,像這樣:.Nav__welcomeWrapper___lKXTg,這樣的名稱仍舊有點長,還帶了冗餘信息。

ShadowRoot

ShadowRoot 是 Shadow DOM 下面的根,你可以把它當做 DOM 中的 <body> 一樣看待,但是它不是 <body>,所以你不能使用 <body> 上的一些屬性,甚至它不是一個節點。

你可以通過 ShadowRoot 下面的 appendChildquerySelectorAll 之類的屬性或方法去操作整個 Shadow DOM 樹。

對於一個普通的 Element,比如 <div>,你可以通過調用它上面的 attachShadow 方法來創建一個 ShadowRoot(還有一個 createShadowRoot 方法,已經過時不推薦使用),attachShadow 接受一個對象進行初始化:{ mode: 'open' },這個對象有一個 mode 屬性,它有兩個取值:'open''closed',這個屬性是在創造 ShadowRoot 的時候需要初始化提供的,並在創建 ShadowRoot 之後成爲一個只讀屬性。

mode: 'open'mode: 'closed' 有什麼區別呢?在調用 attachShadow 創建 ShadowRoot 之後,attachShdow 方法會返回 ShadowRoot 對象實例,你可以通過這個返回值去構造整個 Shadow DOM。當 mode 爲 'open' 時,在用於創建 ShadowRoot 的外部普通節點(比如 <div>)上,會有一個 shadowRoot 屬性,這個屬性也就是創造出來的那個 ShadowRoot,也就是說,在創建 ShadowRoot 之後,還是可以在任何地方通過這個屬性再得到 ShadowRoot,繼續對其進行改造;而當 mode 爲 'closed' 時,你將不能再得到這個屬性,這個屬性會被設置爲 null,也就是說,你只能在 attachShadow 之後得到 ShadowRoot 對象,用於構造整個 Shadow DOM,一旦你失去對這個對象的引用,你就無法再對 Shadow DOM 進行改造了。

可以從上面 Shadow DOM 的截圖中看到 #shadow-root (user-agent) 的字樣,這就是 ShadowRoot 對象了,而括號中的 user-agent 表示這是瀏覽器內部實現的 Shadow DOM,如果使用通過腳本自己創建的 ShadowRoot,括號中會顯示爲 openclosed 表示 Shadow DOM 的 mode。

Open 的 ShadowRoot 與 不Open 的 ShadowRoot

瀏覽器內部實現的 user-agent 的 mode 爲 closed,所以你不能通過節點的 ShadowRoot 屬性去獲得其 ShadowRoot 對象,也就意味着你不能通過腳本對這些瀏覽器內部實現的 Shadow DOM 進行改造。

HTML Template

有了 ShadowRoot 對象,我們可以通過代碼來創建內部結構了,對於簡單的結構,也許我們可以直接通過 document.createElement 來創建,但是稍微複雜一些的結構,如果全部都這樣來創建不僅麻煩,而且代碼可讀性也很差。當然也可以通過 ES6 提供的反引號字符串(const template = `......`;)配合 innerHTML 來構造結構,利用反引號字符串中可以任意換行,並且 HTML 對縮進並不敏感的特性來實現模版,但是這樣也是不夠優雅,畢竟代碼裏大段大段的 HTML 字符串並不美觀,即便是單獨抽出一個常量文件也是一樣。

這個時候就可以請 HTML Template 出場了。我們可以在 html 文檔中編寫 DOM 結構,然後在 ShadowRoot 中加載過來即可。

HTML Template 實際上就是在 html 中的一個 <template> 標籤,正常情況下,這個標籤下的內容是不會被渲染的,包括標籤下的 img、style、script 等都是不會被加載或執行的。你可以在腳本中使用 getElementById 之類的方法得到 <template> 標籤對應的節點,但是卻無法直接訪問到其內部的節點,因爲默認他們只是模版,在瀏覽器中表現爲 #document-fragment,字面意思就是“文檔片段”,可以通過節點對象的 content 屬性來訪問到這個 document-fragment 對象。

template document-fragment

通過 document-fragment 對象,就可以訪問到 template 內部的節點了,通過 document.importNode 方法,可以將 document-fragment 對象創建一份副本,然後可以使用一切 DOM 屬性方法替換副本中的模版內容,最終將其插入到 DOM 或是 Shadow DOM 中。

<div id="div"></div>
<template id="temp">
  <div id="title"></div>
</template>
const template = document.getElementById('temp');
const copy = document.importNode(template.content, true);
copy.getElementById('title').innerHTML = 'Hello World!';

const div = document.getElementById('div');
const shadowRoot = div.attachShadow({ mode: 'closed' });
shadowRoot.appendChild(copy);

HTML Imports

有了 HTML Template,我們已經可以方便地創造封閉的 Web 組件了,但是目前還有一些不完美的地方:我們必須要在 html 中定義一大批的 <template>,每個組件都要定義一個 <template>

此時,我們就可以用到已經被廢棄的 HTML Imports 了。雖然它已經被廢棄了,但是未來會通過 ES6 Modules 的形式再進行支持,所以理論上也只是換個加載形式而已。

通過 HTML Imports,我們可以將 <template> 定義在其他的 html 文檔中,然後再在需要的 html 文檔中進行導入(當然也可以通過腳本按需導入),導入後,我們就可以直接使用其中定義的模版節點了。

已經廢棄的 HTML Imports 通過 <link> 標籤實現,只要指定 rel="import" 就可以了,就像這樣:<link rel="import" href="./templates.html">,它可以接受 onloadonerror 事件以指示它已經加載完成。當然也可以通過腳本來創建 link 節點,然後指定 rel 和 href 來按需加載。Import 成功後,在 link 節點上有一個 import 屬性,這個屬性中存儲的就是 import 進來的 DOM 樹啦,可以 querySelector 之類的,並通過 cloneNodedocument.importNode 方法創建副本後使用。

未來新的 HTML Imports 將會以 ES6 Module 的形式提供,可以在 JavaScript 中直接 import * as template from './template.html';,也可以按需 import,像這樣:const template = await import('./template.html');。不過目前雖然瀏覽器都已經支持 ES6 Modules,但是在 import 其他模塊時會檢查服務端返回文件的 MIME 類型必須爲 JavaScript 的 MIME 類型,否則不允許加載。

Custom Elements

有了上面的三個組件標準,我們實際上只是對 HTML 進行拆分而已,將一個大的 DOM 樹拆成一個個相互隔離的小 DOM 樹,這還不是真正的組件。

要實現一個真正的組件,我們就需要用到 Custom Elements 了,就如它的名字一樣,它是用來定義原生組件的。

Custom Elements 的核心,實際上就是利用 JavaScript 中的對象繼承,去繼承 HTML 原生的 HTMLElement 類(或是具體的某個原生 Element 類,比如 HTMLButtonElement),然後自己編寫相關的生命週期函數,處理成員屬性以及用戶交互的事件。

看起來這和現在的 React 很像,在 React 中,你可以這樣創造一個組件:class MyElement extends React.Component { ... },而使用原生 Custom Elements,你需要這樣寫:class MyElement extends HTMLElement { ... }

Custom Elements 的生命週期函數並不多,但是足夠使用。這裏我將 Custom Elements 的生命週期函數與 React 進行一個簡單的對比:

  • constructor(): 構造函數,用於初始化 state、創建 Shadow DOM、監聽事件之類。

    對應 React 中 Mounting 階段的大半部分,包括:constructor(props)static getDerivedStateFromProps(props, state)render()

    在 Custom Elements 中,constructor() 構造函數就是其原本的含義:初始化,和 React 的初始化類似,但它沒有像 React 中那樣將其拆分爲多個部分。在這個階段,組件僅僅是被創建出來(比如通過 document.createElement()),但是還沒有插入到 DOM 樹中。

  • connectedCallback(): 組件實例已被插入到 DOM 樹中,用於進行一些展示相關的初始化操作。

    對應 React 中 Mounting 階段的最後一個生命週期:componentDidMount()

    在這個階段,組件已經被插入到 DOM 樹中了,或是其本身就在 html 文件中寫好在 DOM 樹上了,這個階段一般是進行一些展示相關的初始化,比如加載數據、圖片、音頻或視頻之類並進行展示。

  • attributeChangedCallback(attrName, oldVal, newVal): 組件屬性發生變化,用於更新組件的狀態。

    對應 React 中的 Updating 階段:static getDerivedStateFromProps(props, state)shouldComponentUpdate(nextProps, nextState)render()getSnapshotBeforeUpdate(prevProps, prevState)componentDidUpdate(prevProps, prevState, snapshot)

    當組件的屬性(React 中的 props)發生變化時觸發這個生命週期,但是並不是所有屬性變化都會觸發,比如組件的 classstyle 之類的屬性發生變化一般是不會產生特殊交互的,如果所有屬性發生變化都觸發這個生命週期的話,會使得性能造成較大的影響。所以 Custom Elements 要求開發者提供一個屬性列表,只有當屬性列表中的屬性發生變化時纔會觸發這個生命週期函數。

    這個屬性列表通過組件類上的一個靜態只讀屬性來聲明,在 ES6 Class 中使用一個 getter 函數來實現,只實現 getter 而不實現 setter,getter 返回一個常量,這樣就是隻讀的了。像這樣:

    class AwesomeElement extends HTMLElement {
      static get observedAttributes() {
        return ['awesome'];
      }
    }
  • disconnectedCallback(): 組件被從 DOM 樹中移除,用於進行一些清理操作。

    對應 React 中的 Unmounting 階段:componentWillUnmount()

  • adoptedCallback(): 組件實例從一個文檔被移動到另一個文檔。

    這個生命週期是原生組件獨有的,React 中沒有類似的生命週期。這個生命週期函數也並不常用到,一般在操作多個 document 的時候會遇到,調用 document.adoptNode() 函數轉移節點所屬 document 時會觸發這個生命週期。

在定義了自定義組件後,我們需要將它註冊到 HTML 標籤列表中,通過 window.customElements.define() 函數即可實現,這個函數接受兩個必須參數和一個可選參數。第一個參數是註冊的標籤名,爲了避免和 HTML 自身的標籤衝突,Custom Elements 要求用戶自定義的組件名必須至少包含一個短槓 -,並且不能以短槓開頭,比如 my-elementawesome-button 之類都是可以的。第二個參數是註冊的組件的 class,直接將繼承的子類類名傳入即可,當然也可以直接寫一個匿名類:

window.customElements.define('my-element', class extends HTMLElement {
  ...
});

註冊之後,我們就可以使用了,可以直接在 html 文檔中寫對應的標籤,比如:<my-element></my-element>,也可以通過 document.createElement('my-element') 來創建,用法與普通標籤幾乎完全一樣。但要注意的是,雖然 html 標準中說部分標籤可以不關閉或是自關閉(<br> 或是 <br />),但是隻有規定的少數幾個標籤允許自關閉,所以,在 html 中寫 Custom Elements 的節點時必須帶上關閉標籤。

由於 Custom Elements 是通過 JavaScript 來定義的,而一般 js 文件都是通過 <script> 標籤外聯的,所以 html 文檔中的 Custom Elements 在 JavaScript 未執行時是處於一個默認的狀態,瀏覽器默認會將其內容直接顯示出來。爲了避免這樣的情況發生,Custom Elements 在被註冊後都會有一個 :defined CSS 僞類而在註冊前沒有,所以我們可以通過 CSS 選擇器在 Custom Elements 註冊前將其隱藏起來,比如:

my-element:not(:defined) {
  display: none;
}

或者 Custom Elements 也提供了一個函數來檢測指定的組件是否已經被註冊:customElements.whenDefined(),這個函數接受一個組件名參數,並返回一個 Promise,當 Promise 被 resolve 時,就表示組件被註冊了。

這樣,我們就可以放心的在加載 Custom Elements 的 JavaScript 的 <script> 標籤上使用 async 屬性來延遲加載了(當然,如果是使用 ES6 Modules 形式的話默認的加載行爲就會和 defer 類似)。

script async defer

Custom Elements + Shadow DOM

使用 Custom Elements 來創建組件時,通常會與 Shadow DOM 進行結合,利用 Shadow DOM 的隔離性,就可以創造獨立的組件。

通常在 Custom Elements 的 constructor() 構造函數中去創建 Shadow DOM,並對 Shadow DOM 中的節點添加事件監聽、對特定事件觸發原生 Events 對象。

正常編寫 html 文檔時,我們可能會給 Custom Elements 添加一些子節點,像這樣:<my-element><h1>Title</h1><p>Content</p></my-element>,而我們創建的 Shadow DOM 又擁有其自己的結構,怎樣將這些子節點放置到 Shadow DOM 中正確的位置上呢?

在 React 中,這些子節點被放置在 propschildren 中,我們可以在 render() 時選擇將它放在哪裏。而在 Shadow DOM 中有一個特殊的標籤:<slot>,這個標籤的用處就如同其字面意思,在 Shadow DOM 上放置一個“插槽”,然後 Custom Elements 的子節點就會自動放置到這個“插槽”中了。

有時我們需要更加精確地控制子節點在 Shadow DOM 中的位置,而默認情況下,所有子節點都會被放置在同一個 <slot> 標籤下,即便是你寫了多個 <slot>。那怎樣更精確地對子節點進行控制呢?

默認情況下,<slot>Fallback</slot> 這樣的是默認的 <slot>,只有第一個默認的 <slot> 會有效,將所有子節點全部放進去,如果沒有可用的子節點,將會顯示默認的 Fallback 內容(Fallback 可以是一棵子 DOM 樹)。

<slot> 標籤有一個 name 屬性,當你提供 name 後,它將變爲一個“有名字的 <slot>”,這樣的 <slot> 可以存在多個,只要名字各不相同。此時他們會自動匹配 Custom Elements 下帶 slot 屬性並且 slot 屬性與自身 name 相同的子節點,像這樣:

<template id="list">
  <div>
    <h1>Others</h1>
    <slot></slot>
  </div>
  <div>
    <h1>Animals</h1>
    <slot name="animal"></slot>
  </div>
  <div>
    <h1>Fruits</h1>
    <slot name="fruit"></slot>
  </div>
</template>

<my-list>
  <div slot="animal">Cat</div>
  <div slot="fruit">Apple</div>
  <div slot="fruit">Banana</div>
  <div slot="other">flower</div>
  <div>pencil</div>
  <div slot="animal">Dog</div>
  <div slot="fruit">peach</div>
  <div>red</div>
</my-list>
class MyList extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    const template = document.getElementById('list');
    root.appendChild(document.importNode(template.content, true));
  }
}
customElements.define('my-list', MyList);

這樣就可以得到如圖所示的結構,#shadow-root (open) 表示這是一個開放的 Shadow DOM,下面的節點是直接從 template 中 clone 過來的,瀏覽器自動在三個 <slot> 標籤下放置了幾個灰色的 <div> 節點,實際上這些灰色的 <div> 節點表示的是到其真實節點的“引用”,鼠標移動到他們上會顯示一個 reveal 鏈接,點擊這個鏈接即可跳轉至其真實節點。

My List

這裏我們可以看到,雖然 <my-list> 下的子節點是亂序放置的,但是只要是給定了 slot 屬性,就會被放置到正確的 <slot> 標籤下。注意觀察其中有一個 <div slot="other">flower</div>,這個節點由於指定了 slot="other",但是卻找不到匹配的 <slot> 標籤,所以它不會被顯示在結果中。

在爲 Custom Elements 下的 Shadow DOM 設置樣式的時候,我們可以直接在 Shadow DOM 下放置 <style> 標籤,也可以放置 <link rel="stylesheet">,Shadow DOM 下的樣式都是局部的,所以不用擔心會影響到 Shadow DOM 的外部。並且由於這些樣式僅影響局部🌻,所以對性能也有很大的提升。

在 Shadow DOM 內部的樣式中,也有一些特定的選擇器,比如 :host 選擇器,代表着 ShadowRoot,這類似於普通 DOM 中的 :root,並且它可以與其他僞類組合使用,比如當鼠標在組件上時::host(:hover),當組件擁有某個 class 時::host(.awesome),當組件擁有 disabled 屬性時::host([disabled])……但是 :host 是擁有繼承屬性的,所以如果在 Custom Elements 外部定義了某些樣式,將會覆蓋 :host 中的樣式,這樣就可以輕鬆地實現各式各樣的“主題風格”了。

爲了實現自定義主題,我們還可以使用 Shadow DOM 提供的 :host-context() 選擇器,這個選擇器允許檢查 Shadow DOM 的任何祖先節點是否包含指定選擇器。比如如果在最外層 DOM 的 <html><body> 上有一個 class:.night,則 Shadow DOM 內就可以使用 :host-context(.night) 來指定一個夜晚的主題。這樣可以實現主題樣式的繼承。

還有一種樣式的定義方式是利用 CSS 變量。我們在 Shadow DOM 中使用變量來指定樣式,比如:background-color: var(--bg-colour, #0F0);,這樣就可以在 Shadow DOM 外面指定 --bg-colour 變量來設置樣式了,如果沒有指定變量,將使用默認的樣式顏色 #0F0

有時我們需要在 Shadow DOM 內部使用完全自定義的樣式,比如字體樣式、字體大小,如果任由其繼承可能導致佈局錯亂,而每次在組件外面指定樣式又略顯麻煩,並且也破壞了組件的封裝性。所以,Shadow DOM 提供了一個 all 屬性,只要指定 :host{ all: initial; } 就可以重置所有繼承的屬性。

Demo

Web Components 的 Demo 在網上已經有很多了,這是我 2 年前初次接觸 ES6 與 Web Components 的時候寫的一個 Demo:https://github.com/jinliming2/Calendar-js,一個日曆,當時還是 v0 的規範,並且在 Firefox 下還存在會導致 Firefox 崩潰的 Bug(感覺是 Firefox 在實現 Shadow DOM 時的 Bug)。目前這個 Demo 已經不能在 Firefox 下運行了,因爲 Firefox 已經刪除了 v0 規範,開始實行 v1 標準了,所以近期我可能會重構一下這個 Demo。

總結

Web Components 的概念最初是由 Alex RussellFronteers Conference 2011 提出的,這個概念在當時非常的震撼。2013 年,Google 推出了一個叫做“Polymer)”的 Web Components 框架以推動 Web Components 的發展。

2014 年,Chrome 發佈了早期版本的 Web Components 組件規範,包括 Custom Elements v0、Shadow DOM v0 和 HTML Imports。但是此時的規範都還是實驗性的,現在已經不推薦使用,並且被 Custom Elements v1、Shadow DOM v1 標準取代,而 HTML Imports 卻沒有標準化,將來會被 ES6 Modules 所取代。

v0 規範將會在 Chrome 70 中被標記棄用警告,並在 2019 年 3 月左右從 Chrome 73 中刪除。

而 v1 標準已經在 Chrome 54+ 和 Safari 10.1+ 中支持,並計劃本月(2018 年 10 月)在 Firefox 中正式支持(此前 Firefox 已經支持,但默認禁用,需要在 about:config 中啓用)。

而 HTML Templates 作爲 HTML5 的功能早已被各大瀏覽器接受並支持。

由於 Web Components 涉及到的子項較多,這裏就不給出 Can I Use 的截圖了,讀者可以自行去搜索“Web Components”查看相關兼容性,或是點擊這裏

Can I Use 中包含了 HTML TemplatesHTML ImportsShadow DOM v0Custom Elements v0Shadow DOM v1Custom Elements v1 的相關瀏覽器兼容性和註釋,非常詳細。

原生 HTML 組件基於的 Web Components 不是單一的技術,他是由 W3C 定義的一系列瀏覽器標準組成的,通過瀏覽器自身可以理解的方式去構建組件,這將成爲未來的前端標準。


文 / jinliming2
一條對新鮮事物充滿了好奇心的鹹魚

編 / 熒聲

作者過往文章:

本文已由作者授權發佈,版權屬於創宇前端。歡迎註明出處轉載本文。本文鏈接:https://knownsec-fed.com/2018...

想要訂閱更多來自知道創宇開發一線的分享,請搜索關注我們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,我們會儘可能回覆。

歡迎點贊、收藏、留言評論、轉發分享和打賞支持我們。打賞將被完全轉交給文章作者。

感謝您的閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章