1. 程式人生 > >將微服務理念延伸到前端開發中

將微服務理念延伸到前端開發中

本文描述了採用不同 JavaScript 技術框架的多個團隊中協同構建一個現代化前端 Web 應用所需要的技術、策略和方法。

什麼是微前端?

微前端這個術語最初來自 2016 年的 ThoughtWorks 技術雷達,它將微服務的概念擴充套件到了前端領域。目前的趨勢是構建一個功能豐富且強大的前端應用,即單頁面應用(SPA),其本身一般都是建立在一個微服務架構之上。前端層通常由一個單獨的團隊開發,隨著時間的推移,會變得越來越龐大而難以維護。這就是傳說中的前端巨無霸(Frontend Monolith)。

微前端背後的理念是將一個網站或者 Web App 當成特性的組合體,每個特性都由一個獨立的團隊

負責。每個團隊都有擅長的特定業務領域或是它關心的任務。這裡,一個團隊是跨職能的,它可以端到端,從資料庫到使用者介面完整的開發它所負責的功能。

然而,這個概念並不新鮮,過去它叫針對垂直系統的前端一體化獨立系統。不過微前端顯然是一個更加友好並且不那麼笨重的術語。

一體化的前端

垂直化組織方式

什麼是現代化前端應用

在介紹中我使用了措辭“構建一個現代化前端應用”,讓我們先給出一些這個術語有關的設定。

從一個更廣泛的角度來看,Aral Balkan 曾寫過一個相關的部落格,他把這個概念叫做文件-應用連續統一體。他提出了一個滑動比例尺的概念,在比例尺的最左邊是一個網站,由靜態文件構成,通過連結相互連線;最右邊是一個純行為驅動的,幾乎沒內容的應用程式,比如線上圖片編輯器。

如果你把你的專案定位在這個範圍的左側,那在 Web 伺服器級別的整合會比較合適。在這個模型中,伺服器會收集頁面中各個元件的內容並將其 HTML 字串連線起來返回給使用者。內容更新則採用從服務端重新載入的方式或者通過 ajax 進行部分替換。Gustaf Nilsson Kotte 針對這個主題寫過一篇綜合性的文章

當用戶介面需要提供及時反饋時,即使採用不可靠連線,一個純粹的服務端渲染網站也不夠用。為了實現 Optimistic UISkeleton Screens 這樣的技術你需要在裝置本身對 UI 進行更新。Google 提出的 PWA 巧妙的描述了這種兼顧各方的做法(漸進增強),同時提供 App 一樣的效能體驗。這種型別的應用在上面的比例尺中位於文件-應用連續統一體中間的某個地方。在這裡純粹的服務端方案已經不再夠用,我們必須將主要邏輯放到瀏覽器中,這正是本文會重點描述的。

微前端背後的核心理念

  • 技術無關

    每一個團隊在選擇和升級他們的技術棧時應該能夠做到不需要和其他團隊進行對接。Custom Elements 是一個隱藏實現細節的非常好的方法,同時能夠對外提供一個統一介面。

  • 隔離團隊程式碼

    即使所有的團隊都使用同樣的框架,也不要共享一個執行時。構建獨立的應用,不要依賴於共享狀態或全域性變數。

  • 建立各團隊的字首

    當隔離已經不可能時要商定一個命名規範。對 CSS、Events、Local Storage 和 Cookie 建立名稱空間來避免碰撞並宣告所有權。

  • 本地瀏覽器特性優先於自定義 API

    採用瀏覽器事件進行資料溝通而不是構建一個全域性的釋出者-訂閱者系統。如果你確實需要構建一個跨團隊的 API,那就確保它越簡單越好。

  • 構建自適應網站

    即使 JavaScript 執行失敗或是根本沒有執行,你的特性也應該是能夠使用的。採用通用渲染或漸進式增強來提高可感知的效能。

DOM 就是 API

自定義元素 Custom Elements 面向 Web 元件規範中互操作方面,在瀏覽器中是一個適用於功能整合的基本元素。每個團隊採用自己選擇的 Web 技術構建他們的元件,並將它們封裝到一個 自定義元素 中(比如 \<order-minicart\>\)。這個特定元素的 DOM 宣告(標籤名、屬性和事件)對於其他團隊來說體現為一個協定或者叫公共 API。這樣做的好處是其他人可以使用這個元件及其功能而不需要知道實現細節,他們只需要能夠和 DOM 互動即可。

但僅僅自定義元素是不能滿足解決方案的所有需求的。為了處理漸進增強、通用渲染或路由我們還需要軟體的其他部分。

本文分為兩部分。首先我們會介紹頁面組合(Page Composition) —— 如何使用不同團隊提供的元件組合成一個頁面。然後我們會給出一些示例展示客戶端頁面轉化(Page Transition)的實現。

頁面組合

除了採用不同框架編寫的客戶端或服務端程式碼整合,還有很多副主題需要討論:隔離 js的機制、規避 CSS 衝突、按需載入資源、不同團隊共享公共資源、處理資料獲取和思考提供給使用者的載入狀態。我們將會依次討論這些主題。

基本原型

如下的拖拉機模型商店的產品頁面將會作為後續示例的基礎。

這個頁面主要功能是通過一個變數選擇器在三個不同拖拉機模型之間進行選擇轉換,變數改版時產品圖片、名稱、價格和推薦都會更新。還有一個購買按鈕,點選後會將選中的模型新增到購物車中,同時頂部的迷你購物車也會相應更新。

所有的 HTML 頁面都通過純 JavaScript和 ES6 模板字串在客戶端生成,沒有任何依賴。程式碼使用一個簡單的狀態/標記分離方式,一旦有變化整個 HTML 頁面都會重新渲染 —— 沒有炫酷的 DOM 對比功能,也暫時沒有通用渲染。當然也沒有團隊分離 —— 所有程式碼都在一個 js/css 檔案中。

客戶端整合

在如下示例中,這個頁面被分隔成不同的元件和片段,分別被三個不同的團隊負責。交易組(藍色)負責所有跟付賬流程有關的事情 —— 也就是購買按鈕迷你購物車推薦組(綠色)負責頁面中的產品推薦部分。頁面本身則由產品組(紅色)負責。

產品組決定哪個功能點被採用以及該功能在頁面佈局的位置。頁面包含的資訊可以由產品組自身提供,比如產品名稱、圖片和可採用的引數,但還可以包括其他團隊提供的片段(自定義元素)。

如何建立一個自定義元素

讓我們把購買按鈕作為一個示例。產品組簡單的將 <blue-buy sku="t_porsche"></blue-buy> 加入到頁面中期望的位置就可以使用這個按鈕了。要讓這個按鈕起作用,交易組還需要在頁面中註冊元素 blue-buy

JavaScript
12345678 classBlueBuyextendsHTMLElement{constructor(){super();this.innerHTML=`<button type="button">buy for66,00</button>`;}disconnectedCallback(){...}}window.customElements.define('blue-buy',BlueBuy);

現在每當瀏覽器遇到一個新的 blue-buy 標籤時,都會呼叫這個構造器。其中,this 是這個自定義元素 DOM 根節點的引用。所有標準 DOM 元素的屬性和方法都可以使用,比如 innerHTMLgetAttribute()

根據標準文件的定義,當命名自定義元素時唯一的需求是名稱中必須包含一個破折號 – 以確保和未來新的 HTML 標籤進行相容。在後面的示例中則使用了 [team_color]-[feature] 命名規範。團隊名稱空間預防了碰撞,這種方法讓一個功能點的權責變得更分明:只要看看 DOM 就知道了。

父子元素通訊 / DOM 修改

當用戶在變數選擇器中選擇了另外一個拖拉機時,購買按鈕必須相應的進行更新。要達到這種效果,產品組只需要從 DOM 中移除相應元素,並插入一個新的。

JavaScript
123 container.innerHTML;// => <blue-buy sku="t_porsche">...</blue-buy>container.innerHTML='<blue-buy sku="t_fendt"></blue-buy>';

老元素的 disconnectedCallback 方法會被同步呼叫進行一些清理資源的操作比如移除事件監聽器。然後新建立的 t_fendt 元素的 constructor會被呼叫。

另外一個性能更好的選擇是僅僅更新現有元素的 sku 屬性。

JavaScript
1 document.querySelector('blue-buy').setAttribute('sku','t_fendt');

如果產品組使用了以 DOM 對比為特色的模板引擎,比如 React,那它的演算法就會自動完成上述功能。

要支援這種效果,自定義元素可以實現 attributeChangedCallback 並指定一個 observedAttributes 列表來觸發這個回撥。

JavaScript
12345678910111213141516171819202122232425 constprices={t_porsche:'66,00 €',t_fendt:'54,00 €',t_eicher:'58,00 €',};classBlueBuyextendsHTMLElement{staticget observedAttributes(){return['sku'];}constructor(){super();this.render();}render(){constsku=this.getAttribute('sku');constprice=prices[sku];this.innerHTML=`<button type="button">buyfor${price}</button>`;}attributeChangedCallback(attr,oldValue,newValue){this.render();}disconnectedCallback(){...}}window.customElements.define('blue-buy',BlueBuy);

為避免重複,引入一個 render() 方法並在 constructorattributeChangedCallback 中呼叫。這個方法收集需要的資料,並填充新標籤的 innerHTML 屬性。當決定在自定義元素中採用一個更加成熟的模板引擎或框架時,這裡便是初始化程式碼所呆的地方。

瀏覽器支援

上例採用了 Custom Element 規範 V1 版,目前已經在 Chrome, Safari 和 Opera 中得到支援。但是通過 document-register-element 這個輕量級且經過大量測試的 polyfill 可以讓該特性在所有瀏覽器中執行。在底層,它使用了廣泛支援的 Mutation Observer API,所以並沒有在背後使用 DOM 樹監聽這種侵入式的 hack 方法。

框架相容性

因為自定義元素 Custom Element 是一個 Web 標準,所有的主流 JavaScript 框架都支援,比如 Angular、React、Preact、Vue 或 Hyperapp。但深入到細節時,就會發現有些框架依然存在實現上的問題。可以訪問 Custom Elements Everywhere 這個相容性測試套件,Rob Dodson 把沒有解決的問題都高亮顯示了。

子父元素或兄弟元素通訊 / DOM 事件

然而,對於所有的互動來說從上至下傳遞屬性是不夠的。在我們的示例中,當用戶對購買按鈕執行一次點選事件時,迷你購物車應該重新整理。

上面這兩個片段都由交易組(藍色)維護的,所以為了達到迷你購物車和按鈕通訊的效果他們可以構建一種內建的 JavaScript API 進行通訊。但這樣就需要元件例項之間相互瞭解,同時也違背了隔離的原則。

一種更加乾淨的方法是採用釋出者訂閱者機制:一個元件可以釋出資訊,其他元件則訂閱指定的主題(topic)。幸運的是瀏覽器內建了這個特性,這也正是 clickselectmouseover 等瀏覽器事件的工作機制。除了這些本地事件,還有一種可能性是通過 new CustomEvent(...) 來建立更加高級別的事件。事件總是繫結到它們建立或者分配的 DOM 節點上,大部分本地事件也支援冒泡的特性,這讓監聽 DOM 中特定子樹節點的所有事件成為可能。如果你想要監聽頁面上的所有事件,將事件監聽器附加到 window 元素上就 OK 了。如下是本示例中 blue:basket:changed 事件建立的大概樣子:

JavaScript
1234567891011121314151617181920 classBlueBuyextendsHTMLElement{[...]connectedCallback(){[...]this.render();this.firstChild.addEventListener('click',this.addToCart);}addToCart(){// maybe talk to an apithis.dispatchEvent(newCustomEvent('blue:basket:changed',{bubbles:true,}));}render(){this.innerHTML=`<button type="button">buy</button>`;}disconnectedCallback(){this.firstChild.removeEventListener('click',this.addToCart);}}

現在迷你購物車可以在 window 物件上訂閱這個事件了,在需要重新整理資料時它就會得到通知。

JavaScript
123456789101112 classBlueBasketextendsHTMLElement{connectedCallback(){[...]window.addEventListener('blue:basket:changed',this.refresh);}refresh(){// fetch new data and render it}disconnectedCallback(){window.removeEventListener('blue:basket:changed',this.refresh);}}

採用這種方法實現時,迷你購物車片段增加了一個不在它範圍之內(window)的 DOM 元素監聽器。對於大部分應用來說,這個做法沒有什麼問題,但是如果你不太滿意這種做法,還可以讓頁面自身(產品組)去監聽這個事件,並通過呼叫 DOM 元素的 refresh() 方法來通知迷你購物車。

JavaScript
123456 // page.jsconst$=document.getElementsByTagName;$('blue-buy')[0].addEventListener('blue:basket:changed',function(){$('blue-basket')[0].refresh();});

命令式呼叫 DOM 方法其實相當罕見,但比如在 video 元素 API 中就有這種做法。如果可能的話,還是應該推薦這種命令式的方法(屬性更改)。

服務端渲染 / 通用渲染

在瀏覽器中採用自定義元素 Custom Elements 來整合元件是個絕好的做法。但實際在構建一個 Web 中可訪問的站點時,很可能是初次載入效能才是關鍵點,在所有的 JS 框架全部載入並執行之前使用者只會看到白屏。另外,還有一個值得思考的是如果 JavaScript 執行失敗或者被阻塞時網站會發生什麼。Jeremy Keith 在他的 ebook/播客 Resilient Web Design 中解釋了這個問題的重要性。所以能夠在服務端渲染核心內容才是關鍵。不幸的是 Web 元件規範根本沒有討論服務端渲染。JavaScript 沒有,Custom Elements 也沒有:(

自定義元素 + 服務端包含(Includes) = ❤️

為了引入服務端渲染,前面的示例進行了重構。每個團隊都有他們自己的 express 伺服器,自定義元素的 render() 方法也都通過 url 來進行訪問。

JavaScript
12 $curl http://127.0.0.1:3000/blue-buy?sku=t_porsche<button type="button">buy for66,00</button>

自定義元素的標籤名被用作路徑名,屬性名成為了查詢引數。這樣為每個元件用服務端渲染內容的方法就有了。再配合上 <blue-buy> 自定義元素,一種非常接近於通用 Web 元件的東西就出來了:

123 <blue-buy sku="t_porsche"><!--#include virtual="/blue-buy?sku=t_porsche" --></blue-buy>

#include 註釋是服務端包含 Server Side Includes 的一部分,這個功能在大部分 Web 伺服器中都支援。沒錯,這個就是很早以前我們在網站中嵌入當前日期所採用的同樣技術。也有幾個其他可選技術比如 ESInodesicompoxuretailor,但是對於我們的專案 SSI 已經被證明是一個簡單同時也相當穩定的解決方案。

在 Web 伺服器將完整的頁面傳送到瀏覽器之前 #include 註釋被替換為 /blue-buy?sku=t_porsche 的返回值。在 Nginx 中配置如下:

JavaScript
123456789101112131415161718192021222324252627 upstreamteam_blue{server team_blue:3001;}upstreamteam_green{server team_green:3002;

相關推薦

服務理念延伸前端開發

本文描述了採用不同 JavaScript 技術框架的多個團隊中協同構建一個現代化前端 Web 應用所需要的技術、策略和方法。 什麼是微前端? 微前端這個術語最初來自 2016 年的 ThoughtWorks 技術雷達,它將微服務的概念擴充套件到了前端領域。目前的趨勢是構建一個功

前端開發的JS調試技巧

pre 整潔 選擇 輸入 越來越大 代碼執行 auto move ctr 前言:調試技巧,在任何一項技術研發中都可謂是必不可少的技能。掌握各種調試技巧,必定能在工作中起到事半功倍的效果。譬如,快速定位問題、降低故障概率、幫助分析邏輯錯誤等等。而在互聯網前端開發越來越重要的今

淺析前端開發的 MVC/MVP/MVVM 模式

所有 團隊 sub 策略 代碼 告訴 簡單 ava 關心 MVC,MVP和MVVM都是常見的軟件架構設計模式(Architectural Pattern),它通過分離關註點來改進代碼的組織方式。不同於設計模式(Design Pattern),只是為了解決一類問題而總結出的抽

前端開發一些容易混淆的概念匯總

提交表單 容易 html text bmi blog 技術分享 mit script ★:HTML中,按鈕button與submit區別是什麽? 1,button 定義可點擊按鈕(多數情況下,用於通過 JavaScript 啟動腳本)。 2,submit 定

說說前端開發的SEO

排序 css代碼 上線 實踐 keywords 加網 itl 實的 欺騙   SEO(Search Engine Optimization),就是傳說中的搜索引擎優化,是指為了增加網頁在搜索引擎自然搜索結果中的收錄數量以及提升排序位置而做的優化行為。我認為這是一門說來簡單,

信小程序日常開發常遇到的錯誤代碼

pen 定義 子菜單 過程 pos 一個 htm 微信小程序 -h 在開發過程中,會遇到很多微信返回的狀態碼,鬼知道代表什麽意思,現在好了,整理總結了一份狀態碼,方便大家。 微信小程序錯誤碼參考 狀態碼(場景值) 說明 -1 系統繁忙 0 請求成功 4

人工智能應用雲開發,“碼農1號”要掀起IT界的一股浪潮

雲開發 人工智能 互聯網隨著人工智能的高速發展,隨處可見識AI技術在生活中的實踐。手機中的siri,家庭中的智能音箱,在醫療診斷上進行智能病例處理,以計算機視覺為核心的城市智能安防系統,以及最近熱議的無人駕駛。 最近有一款人工智能的新應用,在上線以後,就成為IT行業的關註焦點。“碼農1號”,將AI技術應用到雲

人工智能應用雲開發會怎樣

人工智能 雲開發隨著人工智能的高速發展,隨處可見識AI技術在生活中的實踐。手機中的siri,家庭中的智能音箱,在醫療診斷上進行智能病例處理,以計算機視覺為核心的城市智能安防系統,以及最近熱議的無人駕駛。 最近有一款人工智能的新應用,在上線以後,就成為IT行業的關註焦點。“碼農1號”,將AI技術應用到雲開發中,

程序與頁面—信小程序前端開發工具

註冊 cti .json 更多 page ini 回調 new href 你可以觀察到 pages/logs/logs 下其實是包括了4種文件的,微信客戶端會先根據 logs.json 配置生成一個界面,頂部的顏色和文字你都可以在這個 json 文件裏邊定義好。緊接著客

信小程序用戶身份-信小程序前端開發工具

png 聯系 tro 文字 前端 clas 無需 回退 .com 一個團隊進行小程序的開發,那麽團隊成員的身份管理是很有必要的。 管理員可在小程序管理後臺統一管理項目成員(包括開發者、體驗者及其他成員)、設置項目成員的權限,包括:開發者/體驗者權限、登錄小程序管理後臺、

信小程序前端開發教程-個人中心

方法 load src 小程序前端 mdk 視頻 XML 工作效率 css 小程序前端制作-個人中心,內含服務器端、小程序所有文件、切片文件(可以用小程序切片軟件打開再次編輯)、效果圖 內含以下欄目: 會員頭像 會員名稱 會員類型

信小程序前端開發架構

https 問題 bindview xtend 原則 最小化 log code cut 一、目標 1、提升開發速度2、更好的響應需求變化 二、面臨的問題 業務需求的變化是永恒的,而變化勢必會引發代碼的變化,新增代碼或修改代碼。 改動的地方越多,引入新的錯誤幾率就越大。 三

一探前端開發的JS調試技巧

位置 開發者 這一 原因 ctr hrd tree 繼續 價值 前言:調試技巧,在任何一項技術研發中都可謂是必不可少的技能。掌握各種調試技巧,必定能在工作中起到事半功倍的效果。譬如,快速定位問題、降低故障概率、幫助分析邏輯錯誤等等。而在互聯網前端開發越來越重要的今天,如何在

前端開發提到的“腳手架”到底指什麼,CLI?gulp 和 gulp-cli有什麼區別

一般來說,腳手架是幫你減少「為減少重複性工作而做的重複性工作」的工具. gulp和gulp-cli的區別可以看這個task - what does gulp-"cli" stands for? . 它跟前端常說的腳手架(scaffold)不是一個東西. CLI只是Command Line Interface

編寫HTML和CSS的前端開發不一定熟悉JavaScript

作為前端開發人員,HTML、css、JavaScript是必備的知識技能,但是現實工作工作中並非所有的前端都知道JavaScript,根據外國一個網站的匿名調查發現,有17%的開發人員不知道JavaScript,只有51% 的開發人員熟悉JavaScript。其實前端開發最主要的動態開發就是Java

服務註冊到Eureka Server

一、微服務程式編寫 1、在已寫好的微服務程式中新增pom依賴: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sta

Spring Cloud-03服務註冊到Eureka Server上 + 為Eureka Server新增使用者認證

文章目錄 概述 將使用者微服務micorservice-provider-user註冊到Eureka Server上 pom中增加 spring-cloud-starter-netflix-eureka-client 依賴 啟動類新增@

信小程式前端開發踩坑(一)

之前由於不瞭解微信小程式的整個的執行開發機制,走了很多的彎路,腦子靈光的可能不會遇到,這個主題系列的帖子希望可以幫助到像我一樣理解能力慢的孩子。 不論是開發微信小程式還是說學習任何一門程式語言,最重要的一點是要夯實基礎,不是隻是去看看概念,從hello word開始就要好好的去對待每一行程式碼,軟工是工科,

CDN在前端開發的作用

CDN在前端開發的作用 CDN 內容分發網路:content delivery network ​ 概況:設定多個節點伺服器,分佈在不同區域中,便於使用者進行資料傳遞和訪問。當某一個節點出現問題時,通過其他節點仍然可以完成資料傳輸工作,可以提高使用者訪問網站的響應速度。 ​

前端開發,頁面渲染指什麼

從上面這個圖中,我們可以看到那麼幾個事: 1)瀏覽器會解析三個東西: 一個是HTML/SVG/XHTML,事實上,Webkit有三個C++的類對應這三類文件。解析這三種檔案會產生一個DOM Tree。 CSS,解析CSS會產生CSS規則樹。 Javascript,指令碼,主