怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

京程一燈 前端先鋒

每日前端夜話0x24
每日前端夜話,陪你聊前端。
每天晚上18:00準時推送。
正文共:7212 字
預計閱讀時間: 18 分鐘



怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

翻譯:瘋狂的技術宅
原文:https://www.smashingmagazine.com/2018/07/reusable-components-custom-elements-shadow-dom-npm/

摘要:本文着眼於使用具有內置功能和樣式的組件來擴充HTML。 我們還將學習如何通過 NPM 使這些自定義元素在項目中得到重用。


即便是最簡單的組件,人力成本也可能很高。 UX 團隊進行要可用性測試。 涉及到的利益相關者必須對設計簽字確認。

之後是開發人員進行 AB 測試,可訪問性審計,單元測試和跨瀏覽器檢查。 一旦解決了這個問題,你就不想再次重複這項工作了。 通過構建可重用的組件庫(而不是從頭開始構建所有內容),我們就可以不斷複用過去的工作,避免重新審視已經解決的設計和開發過程。

怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]
構建組件庫對於像Google這樣的公司尤爲重要,他們擁有很多具有相同品牌的網站。 通過把 UI 編碼爲可組合小部件,這些大公司既可以減少開發時間,又可以實現跨項目的可視化和用戶交互設計的一致性。在過去幾年中,人們對樣式指南和模式庫的興趣不斷增加。由於開發人員和設計師一般都分佈在多個團隊中,所以大公司需要尋求實現一致性的方法,比如提供簡單的顏色樣本。不過對於我們來說,可以比他們做得更好。 我們需要的是易於分發的代碼。

共享和重用代碼

手動複製和粘貼代碼很容易。但是把代碼保持在最新版是維護上的噩夢。所以許多開發者依賴包管理器來跨項目重用代碼。 儘管名字是 Node Package Manager, 但是它已成爲前端包管理的獨一無二的平臺。 目前在 NPM 上註冊的包超過700,000個,每月下載數十億次。 含有 package.json 文件的任何文件夾都可以作爲可共享包上傳到NPM。 雖然NPM主要與JavaScript相關聯,但包中也可以包含 CSS 和標記。 NPM使重用變得很容易,這對更新代碼尤爲重要:你無需在各種地方修改代碼,所做的是隻需在包中更新代碼即可。

標記存在的問題

使用 import 語句可以對Sass和Javascript 進行輕鬆移植。 模板語言賦予了 HTML 相同的能力 —— 模板能以局部形式導入到 HTML 的其他片段。 比如你可以只需爲頁腳編寫一次標記,然後將其包含在其他模板中即可。 另一種方法是複製並粘貼標記,並只對樣式和 javascript 使用NPM。

這是英國“金融時報”在 Origami 組件庫【http://origami.ft.com/】中用到的方法。Alice Bartlett在她的演講中總結道:“你不能讓它更像是 Bootstrap 嗎?”【https://www.youtube.com/watch?v=LAVNOQroZYA】,“並沒有什麼好辦法能讓人們在他們的項目中包含模板”。
Ian Feather談到他在 Lonely Planet 維護組件庫的經歷,重申了這種方法存在的問題【https://www.ianfeather.co.uk/a-maintainable-style-guide/】:

“一旦複製了這些代碼,他們基本上就會削減一個需要無限期維護的版本。當複製工作組件的標記時,它具有到該點的CSS快照的隱式鏈接。 如果你隨後更新模板或重構CSS,則需要更新分散在你網站周圍的所有模板的版本。“

解決方案:WEB組件

Web組件通過在 JavaScript 中定義標記來解決這個問題。 組件的作者可以自由地修改標記、CSS 和 Javascript。 組件的使用者可以在這些升級中受益,無需手動修改項目代碼。 只需要通過在終端的敲出簡短的 npm update 命令,就可以在項目範圍內更新到最新版本。當然前提是組件的名稱及其 API 需要保持一致。

安裝Web組件就像在終端中鍵入 npm install component-name 一樣簡單。 Javascript 可以包含在 import 語句中:


1<script type="module">
2import './node_modules/component-name/index.js';
3</script>

怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]
在CodePen上的代碼演示:https://codepen.io/cssgrid/pen/KemvbM

在前端開發中,以組件爲中心的方法已經變得無處不在,Facebook 的 React 框架就使用了這種方法。考慮到在現代前端開發工作中框架的普遍性,許多公司已經在用他們選擇的框架構建了組件庫。這些組件只能在該特定框架內重用。
怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

IBM Carbon Design System的一個組件。 僅能用於 React 應用。其他在 React 中構建的組件庫的主要案例包括Atlassian 的 Atlaskit 【http://atlaskit.atlassian.com/packages】 和 Shopify 的 Polaris【https://polaris.shopify.com/components/get-started/】。

對規模較大的公司來說,很少有統一的前端,從一個框架轉到另一個框架的重新佈局並不罕見。各種框架你方唱罷我登場。 爲了在項目中實現最大程度的潛在重用,我們需要與框架無關的組件。
怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

通過在npmjs.com對組件的搜索結果揭示了一個支離破碎的Javascript生態系統。
怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

隨着時間的推移,框架也在不斷變化。

“多年來我使用 Dojo、Mootools、Prototype、jQuery、Backbone、Thorax 和 React 構建了 Web 應用……我希望能把我開發的 Dojo 組件用到現在的 React 應用中。“
—— 谷歌工程總監Dion Almaer

當我們談論Web組件時,我們討論的是自定義元素與 shadow DOM 的組合。 自定義元素和 shadow DOM 是W3C DOM 規範和 WHATWG DOM 標準的一部分 —— 這意味着 Web 組件是 Web 標準的一部分。自定義元素和 shadow DOM 最終會實現跨瀏覽器支持。 通過使用原生 Web 平臺的標準部分,我們確保自己的組件能夠在前端重組和不斷重構的快速變化週期中生存下來。 Web組件可以與任何一種模板語言和前端框架一起使用 —— 它們是真正交叉兼容和可互操作的。 從 Wordpress 博客到單頁應用程序,可以在任何場合下使用。
怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]

Rob Dodson的 Custom Elements Everywhere 項目【https://custom-elements-everywhere.com/】記錄了 Web 組件與各種客戶端 Javascript 框架的互操作性。 這裏面 React 出現的異常值,希望能在 React 17 中解決。

製作Web組件

定義一個自定義元素

生成 made-up-tag 標記並使其內容顯示在頁面上。


1<made-up-tag>Hello World!</made-up-tag>

HTML 被設計爲能夠容錯。即使不是有效的HTML元素,它的內容也會被呈現。 並沒有一個很好的理由這樣做 —— 偏離標準化標籤在傳統上是很差勁的做法。 但是通過用自定義元素 API 定義新的標記,我們就可以用具有內置功能的可重用元素來擴充HTML。 創建自定義元素很像在 React 中創建一個組件 —— 但在這裏是擴展了 HTMLElement 。


1class ExpandableBox extends HTMLElement {
2      constructor() {
3        super()
4      }
5    }

構造函數中的第一個語句必須是對 super() 的無參數調用。構造函數應該用於設置初始狀態和默認值,以及設置事件偵聽器。 需要使用其 HTML 標記的名稱和對應的類的元素定義新的自定義元素:


1customElements.define('expandable-box', ExpandableBox)

把類名大寫是一種慣例。 HTML 標記的語法不僅僅是一種約定,如果瀏覽器想要實現一個新的HTML元素,並且想把它稱爲可擴展框怎麼辦?爲了防止命名衝突,不是最新標準的 HTML 標記要包含破折號。 所以自定義元素的名稱也 必須 包含破折號。


1customElements.define('whatever', Whatever) // 無效
2customElements.define('what-ever', Whatever) // 有效

定製元素生命週期

API 提供了四種自定義元素響應 —— 可以在類中定義函數,這些函數會自動調用來響應自定義元素生命週期中的某些事件。

當自定義元素添加到 DOM 時會執行 connectedCallback。


1connectedCallback() {
2    console.log("custom element is on the page!")
3}

這包括用 Javascript 添加元素:


1document.body.appendChild(document.createElement("expandable-box")) // "custom element is on the page"

以及簡單地在頁面中包含帶有 HTML 標記的元素:


1<expandable-box></expandable-box> // "custom element is on the page"
2

所有涉及到獲取資源或渲染的工作都應該在這裏。

從 DOM 中刪除自定義元素時,將運行 disconnectedCallback。


1disconnectedCallback() {
2    console.log("element has been removed")
3}
4document.querySelector("expandable-box").remove() //"element has been removed"

當自定義元素被採用到新文檔中時,將會運行 adoptedCallback 。 你可能不需要太關心這個問題。

在添加、更改或刪除屬性時運行 attributeChangedCallback 。 它可以用於監聽標準化本機屬性(如 disabled 或 src )的更改,以及我們定義的任何自定義屬性。 這是自定義元素最強大的功能之一,因爲它可以創建用戶友好的 API。

定製元素屬性

因爲有很多 HTML 屬性,所以當任何屬性發生變化時,瀏覽器都不會浪費時間去調用我們的 attributeChangedCallback ,因此需要提供一個我們想要監聽的屬性更改列表。對於這個例子,我們只對一個感興趣。


1static get observedAttributes() {
2            return ['expanded']
3        }

所以現在 attributeChangedCallback 只會在我們更改自定義元素上expanded屬性的值時被調用,因爲它是我們列出的唯一屬性。

HTML 屬性可以有相應的值(例如 href,src,alt,value 等),而其他值可以是true或false (例如 disabled, selected, required)。 對於具有相應值的屬性,我們將在自定義元素的類定義中包含以下內容。


1get yourCustomAttributeName() {
2  return this.getAttribute('yourCustomAttributeName');
3}
4set yourCustomAttributeName(newValue) {
5  this.setAttribute('yourCustomAttributeName', newValue);
6}

對於例子中的元素,其屬性爲 true 或 false,因此定義 getter 和 setter 會有所不同。


 1get expanded() {
 2  return this.hasAttribute('expanded')
 3}
 4
 5// setAttribute的第二個參數是必需的,所以我們將用一個空字符串填充
 6set expanded(val) {
 7  if (val) {
 8    this.setAttribute('expanded', '');
 9  }
10  else {
11    this.removeAttribute('expanded')
12  }
13}

既然已經處理了樣板文件,我們可以使用 attributeChangedCallback 。


1attributeChangedCallback(name, oldval, newval) {
2  console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`);
3  // 每次屬性被更改時執行某些操作
4}

配置 Javascript 組件會涉及將參數傳遞給 init 函數。 通過使用 attributeChangedCallback ,可以創建一個可以使用標記配置的自定義元素。

Shadow DOM 和自定義元素可以單獨使用,你可以找到對自己有用的自定義元素。 與 shadow DOM 不同,它們可以是 Polyfill。 不過它們配合得很好。

使用SHADOW DOM附加標記和樣式

到目前爲止,我們已經處理了自定義元素的行爲。 但是關於標記和樣式,我們的自定義元素相當於空的無樣式 <span> 。 要將HTML和CSS封裝爲組件的一部分,還需要附加一個shadow DOM。 最好在構造函數中執行此操作。


1class FancyComponent extends HTMLElement {
2        constructor() {
3            super()
4            var shadowRoot = this.attachShadow({mode: 'open'})
5            shadowRoot.innerHTML = `<h2>hello world!</h2>`
6            }

不要爲理解模式的含義擔心——你必須包含它的樣板,但你幾乎總是想要 open。 這個簡單的例子組件將只呈現文本“hello world”。 與大多數其他 HTML 元素一樣,自定義元素可以包含子元素 —— 但默認情況下不是。 到目前爲止,前面的自定義元素還不能將任何子元素渲染到屏幕上。 要顯示標記之間的內容,還需要用到 slot 元素。


1shadowRoot.innerHTML = `
2<h2>hello world!</h2>
3<slot></slot>
4`

我們可以用樣式標記將 CSS 應用於組件。


1shadowRoot.innerHTML = 
2`<style>
3p {
4color: red;
5}
6</style>
7<h2>hello world!</h2>
8<slot>some default content</slot>`

這些樣式僅適用於組件,因此我們可以自由地使用元素選擇器,而不會影響頁面的任何其他內容。 這就把編寫 CSS 的過程變得非常簡單,使 BEM 這樣的命名約定變得不必要。

通過 NPM 發佈組件

NPM 包通過命令行進行發佈。 打開一個終端窗口並切換到你想要變成可重用包的目錄中,然後在終端中鍵入以下命令:

  1. 如果你的項目還沒有 package.json【https://docs.npmjs.com/files/package.json】, npm init 將會引導你創建一個。
  2. npm adduser 把你的機器鏈接到你的 NPM 帳戶。 如果你還沒有註冊,它將爲你創建一個新的賬號。
  3. npm publish
    怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]
    如果一切順利的話,在 NPM 列表中會出現你的組件,可以在你自己的項目中安裝和使用 —— 並與全世界共享。
    怎樣開發可重用組件併發布到NPM [每日前端夜話0x24]


Web組件API並不完美。自定義元素目前還無法在表單提交中包含數據【https://github.com/w3c/webcomponents/issues/187】。 漸進式增強並不是很好,對可訪問性的處理並不怎麼容易【https://github.com/WICG/aom/blob/gh-pages/explainer.md#use-case-1-setting-non-reflected-default-accessibility-properties-for-web-components】。

儘管最初在 2011 年宣佈,但瀏覽器還沒有普遍支持。 Firefox 將在今年(2018)晚些時候提供支持。 儘管如此,一些備受矚目的網站(如 Youtube )已經在使用它們。 儘管目前還存在缺點,但對於普遍可共享的組件而言,它們是唯一的選擇,並且在未來它們提供的令人興奮的新功能非常值得期待【https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md】。

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