從0實現一個前端微服務(上)

沉末_發表於2020-01-13

前端微服務的幾種實現方式

什麼是前端微服務,網上大把的介紹,我就不囉嗦了,簡單來說,就是把各個子專案整合到一起。

《前端架構:從入門到微前端》這本書中介紹,微前端架構一般可以由以下幾種方式進行:

  1. 使用 HTTP 伺服器的路由來重定向多個應用(也就是連結跳轉)
  2. 在不同的框架之上設計通訊、載入機制,諸如 Mooa 和 Single-SPA
  3. 通過組合多個獨立應用、元件來構建一個單體應用
  4. iFrame。使用 iFrame 及自定義訊息傳遞機制
  5. 使用純 Web Components 構建應用
  6. 結合 Web Components 構建

其中比較常見的就是iframesingle-spa,這兩者各有千秋。

iframe和single-spa的優缺點

iframe的優缺點

缺點

  1. 頁面載入問題: 影響主頁面載入,阻塞onload事件,本身載入也很慢,頁面快取過多會導致電腦卡頓。(無法解決)

  2. 佈局問題:iframe必須給一個指定的高度,否則會塌陷。解決辦法:子系統實時計算高度並通過postMessage傳送給主頁面,主頁面動態設定高度,修改子系統或者代理插入指令碼。有些情況會出現多個滾動條,使用者體驗不佳。

  3. 彈窗及遮罩層問題:只能在iframe範圍內垂直水平居中,沒法在整個頁面垂直水平居中。

    • 解決辦法1:通過與框架頁面訊息同步解決,將彈窗訊息傳送給主頁面,主頁面來彈窗,對原專案改動大且影響原專案的使用。
    • 解決辦法2:修改彈窗的樣式:隱藏遮罩層,修改彈窗的位置。修改的辦法就是通過代理伺服器插入css樣式。
    • 補充:iframe裡面的內容無法實現佔滿螢幕的彈窗(非全屏),他只能在iframe範圍內全屏,無法跳出iframe的限制在主頁面全屏,不過這種情況也很少。
  4. 瀏覽器前進/後退問題:iframe和主頁面共用一個瀏覽歷史,iframe會影響頁面的前進後退,大部分時候正常,iframe多次重定向則會導致瀏覽器的前進後退功能無法正常使用,不是全部頁面都會出現,基本可以忽略。但是iframe頁面重新整理會重置(比如說從列表頁跳轉到詳情頁,然後重新整理,會返回到列表頁),因為瀏覽器的位址列沒有變化。

  5. iframe的頁面跳轉到其他頁面出問題,比如兩個iframe之間相互跳轉,直接跳轉會只在iframe範圍內跳轉,所以必須通過主頁面來進行跳轉。不過iframe跳轉的情況很少

  6. 系統之間的通訊需要通過postMessage,存在一定的安全性

優點

  1. 完全隔離了cssjs,避免了各個系統之間的樣式和js汙染
  2. 可以在子系統完全不修改的情況下嵌入進來

single-spa的優缺點

缺點

  1. cssjs需要制定規範,進行隔離。否則容易造成全域性汙染,尤其是vue的全域性元件,全域性鉤子。
  2. 需要子系統配合修改。但是不影響子系統獨立開發部署,路由部分對子系統有一些改動,但是不影響功能。

優點

  1. 載入快,可以將所有系統共用的模組提取出來,實現按需載入,一次載入,其他的複用。
  2. 修改子系統的樣式,不需要代理伺服器,直接修改,因為同屬於一個document
  3. 使用者體驗好、快,內容的改變不需要重新載入整個頁面,避免了不必要的跳轉和重複渲染
  4. 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

補充:

  1. 對於SEOiframe無法解決,但是single-spa有辦法解決(谷歌能支援單頁應用的SEO,百度則需要SSR),但是內部系統,SEO的需求比較少。
  2. 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)
複製程式碼

需要注意的是:

  1. 這裡面的引入js/css檔案路徑都是相對路徑,需要拼上子專案的字首。
  2. v-html插入的DOM片段,外鏈script不會生效,需要手動插入

結果是主系統中#app裡面能渲染出子系統,但是#app裡面動態生成的HTMLimg/video/audio等檔案的路徑是相對的,所以會請求到主系統上,但是這些檔案並不在主系統,所以會404,同樣,按需載入的路由頁面對應的js/css檔案也是相對路徑,會請求出錯。如果路由沒按需載入,則不存在這個問題

結論:可以實現微服務效果,但是需要解決檔案相對路徑的問題,index.html裡面的link/script還可以手動加上,但是動態生成的html裡面的img/video/audio等,以及按需載入路由頁面對應的js/css無法通過代理伺服器解決。

解決思路:

  1. 這裡面的js/css/img/video等都是相對路徑,能否通過webpack打包,將這些路徑全部打包成絕對路徑?這樣就可以解決檔案請求失敗的問題。

  2. 能否像CDN一樣,一個伺服器掛了,會去其他伺服器上請求對應檔案。或者說伺服器之間的檔案共享,主系統上的檔案請求失敗會自動去子伺服器上找到並返回。

  3. 能否手動(或藉助node)將子系統的檔案全部拷貝到主專案伺服器上,node監聽子系統檔案有更新,就自動拷貝過來,並且按js/css/img資料夾合併

查閱webpackvue-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-cli3publicPath設定為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的前端微服務(中),包含完整的打包/開發除錯流程,老專案如何改造等。

相關文章