前端微服務的幾種實現方式
什麼是前端微服務,網上大把的介紹,我就不囉嗦了,簡單來說,就是把各個子專案整合到一起。
《前端架構:從入門到微前端》這本書中介紹,微前端架構一般可以由以下幾種方式進行:
- 使用 HTTP 伺服器的路由來重定向多個應用(也就是連結跳轉)
- 在不同的框架之上設計通訊、載入機制,諸如 Mooa 和 Single-SPA
- 通過組合多個獨立應用、元件來構建一個單體應用
- iFrame。使用 iFrame 及自定義訊息傳遞機制
- 使用純 Web Components 構建應用
- 結合 Web Components 構建
其中比較常見的就是iframe
和single-spa
,這兩者各有千秋。
iframe和single-spa的優缺點
iframe的優缺點
缺點
-
頁面載入問題: 影響主頁面載入,阻塞
onload
事件,本身載入也很慢,頁面快取過多會導致電腦卡頓。(無法解決) -
佈局問題:
iframe
必須給一個指定的高度,否則會塌陷。解決辦法:子系統實時計算高度並通過postMessage
傳送給主頁面,主頁面動態設定高度,修改子系統或者代理插入指令碼。有些情況會出現多個滾動條,使用者體驗不佳。 -
彈窗及遮罩層問題:只能在
iframe
範圍內垂直水平居中,沒法在整個頁面垂直水平居中。- 解決辦法1:通過與框架頁面訊息同步解決,將彈窗訊息傳送給主頁面,主頁面來彈窗,對原專案改動大且影響原專案的使用。
- 解決辦法2:修改彈窗的樣式:隱藏遮罩層,修改彈窗的位置。修改的辦法就是通過代理伺服器插入css樣式。
- 補充:
iframe
裡面的內容無法實現佔滿螢幕的彈窗(非全屏),他只能在iframe範圍內全屏,無法跳出iframe的限制在主頁面全屏,不過這種情況也很少。
-
瀏覽器前進/後退問題:
iframe
和主頁面共用一個瀏覽歷史,iframe
會影響頁面的前進後退,大部分時候正常,iframe
多次重定向則會導致瀏覽器的前進後退功能無法正常使用,不是全部頁面都會出現,基本可以忽略。但是iframe
頁面重新整理會重置(比如說從列表頁跳轉到詳情頁,然後重新整理,會返回到列表頁),因為瀏覽器的位址列沒有變化。 -
iframe
的頁面跳轉到其他頁面出問題,比如兩個iframe
之間相互跳轉,直接跳轉會只在iframe
範圍內跳轉,所以必須通過主頁面來進行跳轉。不過iframe
跳轉的情況很少 -
系統之間的通訊需要通過
postMessage
,存在一定的安全性
優點
- 完全隔離了
css
和js
,避免了各個系統之間的樣式和js
汙染 - 可以在子系統完全不修改的情況下嵌入進來
single-spa的優缺點
缺點
css
和js
需要制定規範,進行隔離。否則容易造成全域性汙染,尤其是vue
的全域性元件,全域性鉤子。- 需要子系統配合修改。但是不影響子系統獨立開發部署,路由部分對子系統有一些改動,但是不影響功能。
優點
- 載入快,可以將所有系統共用的模組提取出來,實現按需載入,一次載入,其他的複用。
- 修改子系統的樣式,不需要代理伺服器,直接修改,因為同屬於一個
document
。 - 使用者體驗好、快,內容的改變不需要重新載入整個頁面,避免了不必要的跳轉和重複渲染
http
請求少,伺服器壓力小。
single-spa和iframe對比
對比項 | single-spa | iframe | 補充 |
---|---|---|---|
載入速度 | single-spa可以將所有系統共用的vue/vuex/vue-router等檔案提取出來,只載入一次,各系統複用,載入速度很快,但是必須保證檔案版本統一 | iframe會佔用主系統的http通道,影響主系統的載入,載入速度很慢 | 兩者都可以通過http快取提高一定的載入速度,但是對於vue這些通用檔案沒法做cdn,因為內部系統很可能無法訪問外網 |
相容性 | single-spa只適用於vue、react、angular編寫的系統,對一些jq寫的老系統無能為力 | iframe則可以嵌入任何頁面 | |
技術難度 | single-spa需要一定的技術儲備,有一些學習成本 | iframe門檻則很低,無需額外學習 | |
侷限性 | single-spa可以嵌入任何部件 | iframe只能嵌入頁面,當然了也可以把一個部件單獨寫成一個頁面 | |
改造成本 | single-spa一定要對子系統進行改造,但是改造的內容並不多很多,半小時即可完成 | iframe可以不對原系統進行改造,但是必須藉助代理伺服器進行插入指令碼和css,增加了代理伺服器也增加了系統的不穩定性(兩臺伺服器中的任何一臺掛掉都會導致系統不可用),伺服器也需要成本。如對原系統進行改造,則工作量和single-spa相當 | 專案的原始檔丟失或者其他一些無法改動原始檔的情況,只能使用iframe |
補充:
- 對於
SEO
,iframe
無法解決,但是single-spa
有辦法解決(谷歌能支援單頁應用的SEO
,百度則需要SSR
),但是內部系統,SEO的需求比較少。 iframe
存在安全隱患,兩個iframe
頁面互相引用則會導致無限巢狀bug
,會導致頁面卡死,目前只能通過代理伺服器檢查iframe
頁面內容來處理
iframe和single-spa的實現及簡單原理
iframe很簡單,一個標籤就實現了。single-spa比較陌生,我會詳細介紹。
single-spa初探
以vue
為例,vue-cli4
生成的專案打包生成的index.html
檔案內容如下(精簡了一些無關的內容):
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>my-app</title>
<link href=/js/about.6b1cbb89.js rel=prefetch>
<link href=/css/app.c8c4d97c.css rel=preload as=style>
<link href=/js/app.6a6f1dda.js rel=preload as=script>
<link href=/js/chunk-vendors.164d8230.js rel=preload as=script>
<link href=/css/app.c8c4d97c.css rel=stylesheet>
</head>
<body>
<noscript>
<strong>We're sorry but my-app doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id=app></div>
<script src=/js/chunk-vendors.164d8230.js> </script>
<script src=/js/app.6a6f1dda.js> </script>
</body>
</html>
複製程式碼
其中最核心的部分是:
<link href=/css/app.c8c4d97c.css rel=stylesheet>
<div id=app></div>
<script src=/js/chunk-vendors.164d8230.js> </script>
<script src=/js/app.6a6f1dda.js> </script>
複製程式碼
猜想:能否藉助node
伺服器,將子系統的index.html
獲取到,然後讀取HTML
,獲取到這幾個標籤,返回給主系統,主系統直接插入到body
中,能否呈現出子系統?
node
代理程式碼實現,操作DOM
使用的是cheerio
外掛:
const http = require('http')
// 引入cheerio模組
const cheerio = require('cheerio')
const axios = require('axios')
const server = http.createServer(function (request, response) {
//請求子系統伺服器,獲取到index.html檔案
axios.get('http://localhost/').then(res => {
response.writeHead(200, {
'Content-Type': 'application/xml' ,
'Access-Control-Allow-Origin': '*'
})
// 載入HTML字串
const $ = cheerio.load(res.data)
$('link').each(function () {
$(this).attr('href', 'http://localhost' + $(this).attr('href'))
})
$('script').each(function () {
$(this).attr('src', 'http://localhost' + $(this).attr('src'))
})
const resp = $('body').prepend($('link[rel=stylesheet]')).html();
response.end(resp)
}).catch(e => {
console.log(e)
})
})
server.listen(8080)
複製程式碼
需要注意的是:
- 這裡面的引入
js/css
檔案路徑都是相對路徑,需要拼上子專案的字首。 v-html
插入的DOM
片段,外鏈script
不會生效,需要手動插入
結果是主系統中#app
裡面能渲染出子系統,但是#app
裡面動態生成的HTML
,img/video/audio
等檔案的路徑是相對的,所以會請求到主系統上,但是這些檔案並不在主系統,所以會404,同樣,按需載入的路由頁面對應的js/css
檔案也是相對路徑,會請求出錯。如果路由沒按需載入,則不存在這個問題
結論:可以實現微服務效果,但是需要解決檔案相對路徑的問題,index.html
裡面的link/script
還可以手動加上,但是動態生成的html
裡面的img/video/audio
等,以及按需載入路由頁面對應的js/css
無法通過代理伺服器解決。
解決思路:
-
這裡面的
js/css/img/video
等都是相對路徑,能否通過webpack
打包,將這些路徑全部打包成絕對路徑?這樣就可以解決檔案請求失敗的問題。 -
能否像CDN一樣,一個伺服器掛了,會去其他伺服器上請求對應檔案。或者說伺服器之間的檔案共享,主系統上的檔案請求失敗會自動去子伺服器上找到並返回。
-
能否手動(或藉助
node
)將子系統的檔案全部拷貝到主專案伺服器上,node
監聽子系統檔案有更新,就自動拷貝過來,並且按js/css/img
資料夾合併
查閱webpack
和vue-cli3
官網後發現:
預設情況下,Vue CLI
會假設你的應用是被部署在一個域名的根路徑上,例如 https://www.my-app.com/
。如果應用被部署在一個子路徑上 https://www.my-app.com/my-app/
,而你使用的是history模式的路由,對於url:https://www.my-app.com/my-app/page1
,vue無法區分my-app
是真實路徑,而page1
是路由引數,這個時候需要設定 publicPath
為 /my-app/
,vue才能正確的請求檔案資源和匹配路由。
這裡可以將vue-cli3
的 publicPath
設定為https://www.my-app.com/my-app/
,然後程式碼裡面的js/css/img/video
路徑都會變成絕對路徑,字首是https://www.my-app.com/my-app/
,這樣就解決了url
路徑的問題。
這樣就可以實現一個簡單的single-spa
應用,但是載入好的Vue
子系統不會在切換到下一個系統的時候解除安裝掉,子系統過多則會導致卡頓,並且css/js
汙染的可能性增加,實用性不大。
最後
文章有什麼疑問or錯誤,歡迎評論。下一篇文章:從0實現一個single-spa的前端微服務(中),包含完整的打包/開發除錯流程,老專案如何改造等。