深入調研了微前端,還是 iframe 最香
微前端是什麼
微前端是一種類似於微服務的架構,它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。各個前端應用還可以獨立運行、獨立開發、獨立部署。
簡單來說,就是利用一系列工具和技術,將各個團隊的 UI 頁面 組裝成用戶可以連貫的應用程序。
後端解耦,前端聚合
採用微服務的原因主要還是在於,使用微服務架構來解耦服務間依賴。
而在前端微服務化上,則是恰恰與之相反的,人們更想要的結果是聚合,尤其是那些 To B(to Bussiness)的應用。
在這兩三年裏,移動應用出現了一種趨勢,用戶不想裝那麼多應用了。而往往一家大的商業公司,會提供一系列的應用。這些應用也從某種程度上,反應了這家公司的組織架構。然而,在用戶的眼裏他們就是一家公司,他們就只應該有一個產品。相似的,這種趨勢也在桌面 Web 出現。聚合成爲了一個技術趨勢,體現在前端的聚合就是微服務化架構。
目的
-
減少團隊件的等待時間
-
不再有前端巨石架構
優點
-
可獨立部署
-
將故障風險的粒度隔離到更小的範圍
-
職責範圍更窄,更加易於理解
-
擁有更小的代碼庫,有利於重構和替換
-
狀態更易於預測,因爲它不與其他系統共享狀態
缺點
-
冗餘
-
各個團隊需要建立維護自己的服務器,構建流程和持續集成的管道,可能還加載冗餘的 js/css
-
一致性
-
後端團隊有獨立的數據庫,團隊之間需要定期複製數據,一旦出現錯誤,容易引起數據不一致
-
異質性
-
技術棧可選擇性多
-
更多的前端代碼
微前端實施方式
路由分發式微前端
通過路由將不同的業務分發到不同的、獨立前端應用上。其通常可以通過 HTTP 服務器的反向代理來實現,又或者是應用框架自帶的路由來解決。
http {
server {
listen 80;
server_name www.phodal.com;
location /api/ {
proxy_pass http://http://172.31.25.15:8000/api;
}
location /web/admin {
proxy_pass http://172.31.25.29/web/admin;
}
location /web/notifications {
proxy_pass http://172.31.25.27/web/notifications;
}
location / {
proxy_pass /;
}
}
}
-
不同技術棧之間差異比較大,難以兼容、遷移、改造
-
項目不想花費大量的時間在這個系統的改造上
-
現有的系統在未來將會被取代
-
系統功能已經很完善,基本不會有新需求
iframe
顧名思義, 通過 iframe 加載子應用。通信可以通過 postMessage 進行通信。
優點
-
簡單
-
隔離
-
安全
缺點
-
佈局約束
-
性能開銷
-
破壞了語義化,對無障礙可訪問性支持不好哦
-
不利於 seo,會當成 2 個頁面
-
url 不同步。瀏覽器刷新 iframe url 狀態丟失、後退前進按鈕無法使用。
-
UI 不同步,DOM 結構不共享。想象一下屏幕右下角 1/4 的 iframe 裏來一個帶遮罩層的彈框,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中..
-
全局上下文完全隔離,內存變量不共享。iframe 內外系統的通信、數據同步等需求,主應用的 cookie 要透傳到根域名都不同的子應用中實現免登效果。
-
慢。每次子應用進入都是一次瀏覽器上下文重建、資源重新加載的過程。
ajax
ajax 請求服務端,直接在主頁面區域返回拼裝好的 html
優點
-
簡單
-
自然的文檔流
-
利於 seo
-
利於無障礙可訪問性
-
漸進式增強
-
靈活的錯誤處理
缺點
-
異步加載
-
缺少隔離性
-
需要向服務器發送請求
-
腳步缺少生命週期
web component
將前端應用程序分解爲自定義 HTML 元素。基於 CustomEvent 實現通信
Shadow DOM 天生的作用域隔離
重寫現有的前端應用,使用 Web Components 來完成整個系統的功能。
-
被 Web 標準廣泛支持
-
自定義元素 shadow DOM 支持隔離
-
引入了生命週期
-
shadow 兼容性支持度不夠好
single-spa
-
實現一套生命週期,在 load 時加載子 app,由開發者自己玩,別的生命週期裏要幹嘛的,還是由開發者造的子應用自己玩
-
監聽 url 的變化,url 變化時,會使得某個子 app 變成 active 狀態,然後走整套生命週期
-
子應用最關鍵的一步就是導出 bootstrap, mount, unmount 三個生命週期鉤子。
-
基於瀏覽器原生的事件系統,無框架耦合,全局開箱可用。
-
load 方法需要知道子項目的入口文件
-
把多個應用的運行時集成起來需要項目間自行處理內存泄漏,樣式污染問題
-
沒有提供父子數據通信的方式
qiankun
qiankun 基於 single-spa 進行了二次開發
-
主應用:只需要輸入子應用的 html 入口
-
子應用:與 single-spa 基本一致,導出了三個生命週期函數。
-
js 隔離
-
Proxy 沙箱,它將 window 上的所有屬性遍歷拷貝生成一個新的 fakeWindow 對象,緊接着使用 proxy 代理這個 fakeWindow,用戶對 window 操作全部被攔截下來,只作用於在這個 fakeWindow 之上
-
css 隔離
-
ShadowDOM 樣式沙箱會被開啓。在這種模式下 qiankun 會爲每個微應用的容器包裹上一個 shadow dom 節點,從而確保微應用的樣式不會對全局造成影響。
-
Scoped CSS,qiankun 會遍歷子應用中所有的 CSS 選擇器,通過對選擇器前綴添加一個固定的帶有該子應用標識的屬性選擇器的方式來限制其生效範圍,從而避免子應用間、主應用與子應用的樣式相互污染。
-
但如果用戶在運行時引入了新的外聯樣式或者自行創建了新的內聯標籤,那麼 qiankun 並不會做出反應
-
qiankun 在框架內部預先設計實現了完善的發佈訂閱模式
無界
使用 iframe 有三個難以解決的問題,
-
路由狀態丟失,刷新一下,iframe 的 url 狀態就丟失了
-
dom 割裂嚴重,彈窗只能在 iframe 內部展示,無法覆蓋全局
-
通信非常困難,只能通過 postmessage 傳遞序列化的消息
無界微前端框架通過繼承 iframe 的優點,解決 iframe 的缺點,打造一個接近完美的 iframe 方案
在應用 A 中構造一個 shadow 和 iframe,然後將應用 B 的 html 寫入 shadow 中,js 運行在 iframe 中,注意 iframe 的 url,iframe 保持和主應用同域但是保留子應用的路徑信息,這樣子應用的 js 可以運行在 iframe 的 location 和 history 中保持路由正確。
在 iframe 中攔截 document 對象,統一將 dom 指向 shadowRoot,此時比如新建元素、彈窗或者冒泡組件就可以正常約束在 shadowRoot 內部。
-
dom 割裂嚴重的問題,主應用提供一個容器給到 shadowRoot 插拔,shadowRoot 內部的彈窗也就可以覆蓋到整個應用 A
-
路由狀態丟失的問題,瀏覽器的前進後退可以天然的作用到 iframe 上,此時監聽 iframe 的路由變化並同步到主應用,如果刷新瀏覽器,就可以從 url 讀回保存的路由
-
通信非常困難的問題,iframe 和主應用是同域的,天然的共享內存通信,而且無界提供了一個去中心化的事件機制
iframe+ web component
-
接入簡單:安裝一個組件即可 wujie-vue
-
css 隔離
-
js 隔離
Shadow DOM 是 Web Components 技術的一部分,它允許開發者創建封裝、可複用的組件。當一個元素使用 Shadow DOM 創建時,它會包含一個 Shadow Root,這是一個獨立的 DOM 子樹,與文檔中的其他部分相互隔離,可以在其中定義和控制樣式和行爲。因此,Shadow DOM 的插拔機制也是非常重要的。
在 Shadow DOM 中,插入和移除節點的過程稱爲 “插拔”。Shadow DOM 提供了以下方法來實現插拔:
-
attachShadow(options) 方法:該方法將返回一個 ShadowRoot 對象,通過該對象可以管理 Shadow DOM 的內容。
-
appendChild(node) 和 removeChild(node) 方法:這些方法允許向 Shadow DOM 中添加或刪除節點。
-
MutationObserver API:使用該 API,可以監視 DOM 樹的變化,並在變化發生時採取適當的行動。
需要注意的是,在 Shadow DOM 中,被插入到 Shadow Root 中的元素有可能難以再次獲取或操作,因爲它們可能不會出現在文檔的正常 DOM 樹中。爲了解決這個問題,我們可以使用 getElementById() 或 querySelector() 等方法,或者在創建自定義元素時定義自定義方法。
渲染子應用步驟
-
創建一個 iframe,插入主應用 document
-
立即停止 iframe 的加載
-
因爲 iframe 的 src 要設置爲主應用的域名,繼續請求資源會失敗
-
修改爲主應用域名是爲了通信
-
修改請求的域名爲子應用的真實域名
-
所以子應用需要能支持跨域
-
解析子應用的入口 html
-
識別出 html 部分,分離 style 和 js
-
處理 css 重新注入 html (有插件系統,可以對子應用的 css 定義)
-
創建 webComponent 並掛載 HTML
-
CSS 由於在 shadowDOM 內,樣式也不會影響到外部,也不會受外部樣式影響。
-
創建 script 標籤,並插入到 iframe 的 head 中
-
對 iframe 的 document.querySelector 進行改造,需要劫持 document 改爲從 shadowRoot 裏面查找,才能使 Vue 組件能夠正確找到掛載點
micro app
micro-app 並沒有沿襲 single-spa 的思路,而是借鑑了 WebComponent 的思想,通過 CustomElement 結合自定義的 ShadowDom,將微前端封裝成一個類 WebComponent 組件,從而實現微前端的組件化渲染。並且由於自定義 ShadowDom 的隔離特性,micro-app 不需要像 single-spa 和 qiankun 一樣要求子應用修改渲染邏輯並暴露出方法,也不需要修改 webpack 配置,是目前市面上接入微前端成本最低的方案。、
它在 基座應用 和 子應用 之間充當橋樑膠水的作用。
接入方式
import microApp from '@micro-zoe/micro-app';
microApp.start();
export function MyPage () {
return (
<div>
<h1>子應用</h1>
<micro-app
name='app1' // name(必傳):應用名稱
url='http://localhost:3000/' // url(必傳):應用地址,會被自動補全爲http://localhost:3000/index.html
baseroute='/my-page' // baseroute(可選):基座應用分配給子應用的基礎路由,就是上面的 `/my-page`
></micro-app>
</div>
)
}
加載子應用過程
microApp.start() 後,會註冊一個名爲 micro-app 的自定義 webComponent
標籤。
通過 fetch 拿到 url 對應的 html 字符串,然後替換 head 和 body 標籤爲自定義標籤,避免污染主應用 micro-app-head micro-app-body
htmlstr.replace(/<head/i, '<micro-app-head')
htmlstr.replace(/<body/i, '<micro-app-body')
處理 link 標籤
-
處理 href 屬性,在原本的 href 的前面拼接上 app.url ,相對路徑改絕對路徑
-
若爲樣式鏈接,ref 的屬性是 stylesheet,刪除該 link, 記錄 href 內容,創建一個 style 標籤插入
<micro-app-head>
-
創建 style 標籤時會,給子應用的 style 標籤添加作用域,實現樣式隔離
處理 style 標籤
- 給子應用的 style 標籤加上作用域,前綴是 ${microApp.tagName}[name=xxx]
例如:
.test { height: 100px; }
添加前綴後變爲:
micro-app[name=xxx] .test { height: 100px; }
處理 script 標籤
- src 屬性上拼接 app.url,重新加載 script 標籤,將其內容保存下來。
掛載子應用
- 當處理完 html 後,將之前處理過的 html 內容放入 webComponent 容器 () 中
綁定沙箱
-
元素隔離,攔截 document 對象,當尋找根元素時,判斷當前的 appName
-
js 隔離,對 window 對象做了一層代理
隔離
MicroApp 借鑑了 qiankun 的 js 沙箱和樣式隔離方案,這也是目前應用廣泛且成熟的方案。
微前端藍圖
新團隊組建後,首先不得不完成很多設置工作,例如創建一個基本的應用程序,梳理構建流程以及其他繁瑣的任務
而共用前端藍圖這一概念能夠幫助我們解決上述問題。藍圖實際上是一個示例項目,其中包括微前端項目需要的所有重要部分。可以將藍圖劃分爲兩大類:技術和項目細節
技術細節
-
目錄結構
-
測試(單元測試,端到端測試)
-
代碼檢查以及格式化規則
-
代碼風格
-
API 通信
-
性能的最佳實踐(優化靜態文件)
-
編譯工具配置
上述這些方向是所有項目都必須要考慮的,但並不具有很大的挑戰性。大多數主流框架都提供了腳手架工具,能爲你生成一個示例項目,但是對於一個團隊來說,僅使用默認的前端配置是遠遠不夠的。
項目細節
你的前端代碼需要和其他團隊進行整合,並且整合必須遵循整體架構的指南。全新的前端項目必須要考慮項目的一些細節。因此,我們的藍圖還應包括:
-
組合示例
-
接入其他微前端的示例
-
令你的微前端可以被接入的示例
-
通信示例
-
如何爲團隊設置 CSS 和 URL 前綴
-
微前端相關文檔的模版
-
如何整合託管在中心化服務中的庫
-
如何引入本地的庫
-
如何開發通用服務,如異常跟蹤,分析等
-
CI/CD 流程
新加入團隊可以複製上面的藍圖,根據實際需要稍加改動,即可變成他們自己的藍圖。基於現有藍圖進行開發能夠大大的節約時間。
參考鏈接
https://www.modb.pro/db/610768
https://qiankun.umijs.org/zh/cookbook
https://microfrontends.cn/
https://zhuanlan.zhihu.com/p/378346507
https://zhuanlan.zhihu.com/p/442815952
https://juejin.cn/post/7215967453913317434
https://juejin.cn/post/7125646119727529992#heading-5
https://segmentfault.com/a/1190000040462400
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/He2q9jlfVE9OBpyJWyGmoA