微前端的溝通 - 發布/訂閱 vs 傳遞自訂事件 (Cross Micro Frontends Communication: Pub/Sub vs Custom Events)
30 Mar 2023本文主要探討如何在微前端的跨應用程式間溝通,尤其針對兩種解法「發布/訂閱 (pub/sub)」與「傳遞自訂事件 (custom events)」說明和比較。
微前端與跨應用程式間的溝通
微前端 (micro frontends) 是一種前端架構,它將一個大型的 app 切分成多個小型、獨立的 app,這樣就能切分成不同單位而能可以獨立實作、運作和部署,適合由不同的團隊進行開發和維護。如果還是不了解「微前端」是什麼,這裡有些不錯的文章可以參考看看。
考量使用微前端架構的因素大多是由於 (1) 專案逐漸龐大,而解耦可維持高效開發;(2) 功能間的相依度低或技術線獨立,不需要綁在一起等。因此,若想使用微前端作為架構方式,儘管應用程式間溝通不是很頻繁,但還是需要些許元件、函式、資料和狀態的共享與傳遞,於是我們就需要一些機制來做跨應用程式間的溝通。
微前端在處理共享程式碼方面,就 function、component 和共用邏輯來說,有很多種共用的方法,像是做成 library 然後發佈成 npm package 或 git submodule 等,個別 app 要用的時候再自行 import 即可。若對共用程式碼相關有興趣,可以參考這篇文章。
微前端在處理共享狀態方面,針對網站上各種不同的功能切分成多個微前端,而這些微前端可能是位於不同 domain 或由不同 framework 實作而成。這些微前端彼此間有些許關聯和連動性,像是必須共享資料或狀態,稍等會來看有哪些溝通方式;但若微前端彼此間需頻繁溝通,就考慮合併它們吧!
微前端的溝通方式有哪些
微前端的溝通方式大致上可分為 5 種:(1) web workers、(2) props + callbacks、(3) custom events、(4) pub/sub library 與 (5) 丟給後端處理。
在考量上手與設定的便利性後,選用兩種方式「發布/訂閱」(pub/sub, windowed-observable) 和「傳遞自訂事件」(custom events) 來實作和比較。以下範例是用 monorepo 的架構與 NX 來設定的,建立一個 container app 與 microfrontend app 來做溝通。
Web Workers
web workers 是指在 container app 建立 web worker 並存在 window 裡面給其他 app 共享,其他個別 app 監聽 window 的變化,以及更新 window 的內容。優點是希望能藉由 worker thead 加速效能,但要注意 main thread 與 worker thead 間溝通的複雜度與資料傳輸的成本。
Props + callbacks
props + callbacks 是指在 container app 建立 state 存放共享狀態來讓個別 app 存取。優點是簡單直覺、易於實作,但這解法很可能在整合不同框架時會遇到較大的風險。
發布/訂閱 (Pub/Sub, windowed-observable)
pub/sub library (windowed-observable) 是指在 container app 將狀態存到 window 並與其他 app 共享,其他個別 app 監聽 window 的變化,以及更新 window 的內容,類似 props + callbacks 的概念,只是細節由 library 周到地處理好了。
簡單範例如下。
在 container app 最一開始會丟出 Hello World!
,點擊 Say Hi
則丟出 Hi!
。
import M1 from '@/components/M1';
const observable = new Observable('messages');
const observer = (item: any) => console.log(item);
observable.subscribe(observer)
observable.publish('Hello World!');
export default function App() {
const [messages, setMessages] = useState([]);
const handleNewMessage = (newMessage: any) => {
console.log('messages', messages)
setMessages((currentMessages) => currentMessages.concat(newMessage));
};
useEffect(() => {
observable.subscribe(handleNewMessage);
return () => {
observable.unsubscribe(handleNewMessage)
}
}, [handleNewMessage]);
return
<>
<M1 />
<button onClick={() => { handleNewMessage('Hi!') }}>Say Hi</button>;
</>
}
接著在 microfrontend app 點按鈕 Reply
則丟出 Say hi again!
。
import { Observable } from 'windowed-observable';
const observable = new Observable('messages');
export default M1() {
return (<button onClick={() => { observable.publish('Say hi again!') }}>Reply!</button>);
}
選用 pub-sub library 的好處是容易上手、易於客製化、不綁死特定框架框架、API 足夠使用、細節都有被詳細處理到;而缺點是暴露在 window 易被修改、非原生 API 而有 (windowed-observable) package dependency 風險是需要考量的。
傳遞自訂事件 (Custom Events)
custom events 是指 app 彼此經由利用 eventListeners 監聽共享狀態的變化,經由 CustomEvent 更新狀態。
簡單範例如下。
在 container app 最一開始會丟出 Hello World!
,點擊 Say Hi
則丟出 Hi!
。
import M1 from '@/components/M1';
export default function App() {
const [messages, setMessages] = useState(['Hello World!']);
const handleNewMessage = (event: any) => {
console.log('messages', messages);
if(event?.detail) {
setMessages((currentMessages) => currentMessages.concat(event.detail));
}
};
useEffect(() => {
window.addEventListener('message', handleNewMessage);
return () => {
window.removeEventListener('message', handleNewMessage)
}
}, [handleNewMessage]);
return (
<>
<M1 />
<button onClick={() => { handleNewMessage('Hi!') }}>Say Hi</button>
</>
);
}
export default App;
接著在 microfrontend app 點按鈕 Reply
則丟出 Say hi again!
。
export default function M1() {
const reply = (msg: any) => {
const customEvent = new CustomEvent('message', { detail: msg });
window.dispatchEvent(customEvent);
}
return (
<>
<button onClick={() => { reply('Say hi again!') }}>Reply</button>
</>
);
}
選用 custom events 的好處同樣有容易上手、易於客製化、不綁死特定框架框架、API 足夠使用,而且是原生 API,就沒有 package dependency 風險,只是有些細節需要自己來處理。
發布/訂閱與傳遞自訂事件兩種解法概念上大致相同,都是基於共享狀態的概念來實作,差異只在於 custom events 是用原生的 API,而 windowed-observable 則幫我們將細節處理好。
丟給後端處理
丟給後端處理是指 app 間要共享的資料,一律丟給後端處理,前端只要打 API 存取就好,推薦使用 GraphQL。但這解法的 UX 體驗也許沒這麼優。
總結
- 微前端的溝通方式大致上可分為 5 種:web workers、props + callbacks、custom events、pub/sub library 與丟給後端處理,這些方案都是不錯的選擇,選用其一或綜合以上方法都好,只要是盡量維持簡單、適合目前專案開發、能滿足需求即可,這篇文章對於微前端的溝通方式列出詳細的比較,有興趣的可以參考看看。
- 就個人開發經驗來說較傾向選用 custom events 或 pub/sub library 來處理微前端的溝通,一方面是團隊上手門檻低,另一方面也是由於簡單就容易除錯與客製化,在整合大型或大量微前端的跨應用程式,像是不同框架、層層堆疊或面對複雜需求時更有彈性。
推薦閱讀
- Micro Frontend 是什麼?
- Micro frontends - An architectural style where independently deliverable frontend applications are composed into a greater whole
- Micro Frontends - extending the microservice idea to frontend development
- Micro Frontends - How to split up your large, complex, frontend codebases into simple, composable, independently deliverable apps.
- Front End Happy Hour Episode 162 - Micro Frontends - Micro Brewing,逐字稿
- Fix spaghetti code and other pasta-theory antipatterns
- 怎麼建構 Micro Frontend?
- Cross Micro Frontend Communication:這篇文章針對以上解法都有很詳細的解釋、範例和比較。