1.基本概念
實現一套微前端架構,可以把其分成四部分(參考:https://alili.tech/archive/11052bf4/)
加載器:也就是微前端架構的核心,主要用來調度子應用,決定何時展示哪個子應用, 可以把它理解成電源。
包裝器:有了加載器,可以把現有的應用包裝,使得加載器可以使用它們,它相當於電源適配器。
主應用:一般是包含所有子應用公共部分的項目—— 它相當於電器底座
子應用:眾多展示在主應用內容區的應用—— 它相當於你要使用的電器
所以是這么個概念:電源(加載器)→電源適配器(包裝器)→️電器底座(主應用)→️電器(子應用)️
總的來說是這樣一個流程:用戶訪問index.html后,瀏覽器運行加載器的js文件,加載器去配置文件,然后注冊配置文件中配置的各個子應用后,首先加載主應用(菜單等),再通過路由判定,動態遠程加載子應用。
2.預備知識
2.1 SystemJs
SystemJS提供通用的模塊導入途徑,支持傳統模塊和ES6的模塊。
SystemJs有兩個版本,6.x版本是在瀏覽器中使用的,0.21版本的是在瀏覽器和node環境中使用的,兩者的使用方式不同。(參考:https://github.com/systemjs/systemjs)
在微服務中主要充當加載器的角色。
2.2 singleSpa
single-spa是一個在前端應用程序中將多個javascript應用集合在一起的框架。主要充當包裝器的角色。(參考:https://single-spa.js.org/docs/getting-started-overview.html)
3.微服務實踐
3.1 創建應用
首先創建一個主應用iframe,這個主應用只需要簡單的起一個服務訪問靜態資源即可。
用npm init初始化,創建一個index.html,簡單寫個hello world,安裝依賴 npm i serve --s。
在package.json中的scripts中增加啟動命令"serve": "serve -s -l 7000"。運行后可以看到hello world。
然后在創建3個子應用,我用的是vue-cli2.0,分別創建navbar應用(用來寫路由),program1(應用1),program2(應用2)。
navbar應用中只放兩個鏈接就好了,如圖:
子應用program1和program2的路由都相應的加上項目名稱前綴,都增加一個about路由來作為子應用的路由切換,大致如下:
3.2 改造子應用
首先包裝子應用,各個子應用都需要安裝依賴 npm i single-spa-vue systemjs-webpack-interop,修改入口文件main.js如下:
其中 single-spa-vue是針對vue項目的包裝器,systemjs-webpack-interop是社區維護的npm庫,它可以幫助您使webpack和systemjs一起正常工作。
除此之外還需要在webpack配置中的output中增加設置
3.3 修改主應用文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <meta name="importmap-type" content="systemjs-importmap"> <script type="systemjs-importmap"> { "imports": { "navbar": "http://localhost:8080/app.js", "program1": "http://localhost:8081/app.js", "program2": "http://localhost:8082/app.js", "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" } } </script> <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-register.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script> </head> <body> <script> (function(){ System.import('single-spa') .then((res)=>{ var singleSpa=res; singleSpa.registerApplication('navbar',()=>System.import('navbar'),location=>true); singleSpa.registerApplication('program1',()=>System.import('program1'),(location)=>{ return location.hash.startsWith(`#/program1`); }); singleSpa.registerApplication('program2',()=>System.import('program2'),(location)=>{ return location.hash.startsWith(`#/program2`); }); singleSpa.start(); }) })() </script> </body> </html>


4.項目整合
以上只是在開發環境中使用,接下來嘗試不分別啟動服務,只啟用一個服務來跑項目。大體思路是使用express搭建一個服務,將子應用全部打包到項目上作為靜態資源訪問,入口html使用ejs模板來實現項目配置,而不再寫死。
4.1 使用express生成器生成項目
4.2 修改子應用打包配置
這樣子應用就全部打包到express應用中作為靜態資源使用了。
4.3 增加應用配置文件
將iframe的index.html的內容復制到express的入口ejs中。增加配置文件apps.config.json和apps.config.js。
apps.config.js作用就是根據apps.config.json在靜態資源文件夾下生成一份新的配置文件,將配置文件中的資源名稱通過正則匹配成完整的資源路徑。並且監聽文件變化來更新靜態資源文件夾下的配置文件。
生成的配置文件
4.4根據這個生成的配置文件去修改ejs,將項目注冊過程循環出來,而不再是寫死的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <meta name="importmap-type" content="systemjs-importmap"> <script type="systemjs-importmap"> { "imports": { <%for(var i=0;i<apps.length;i++){%> "<%= apps[i].name %>":"<%= apps[i].server %><%=apps[i].resourceEntryUrl %>", <%}%> "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" } } </script> <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-register.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script> </head> <body> <script> (function(){ Promise.all([ System.import('single-spa'), System.import('./apps.config.json') ]) .then((res)=>{ var singleSpa=res[0]; var configs=res[1].default; configs.apps.forEach( project => { if(project.resource.length>0){ Promise.all(project.resource.map(i=>{ return System.import(project.server+i) })).then(function(){ singleSpa.registerApplication(project.name,()=>System.import(project.name),(location)=>{ return project.base?true:location.hash.startsWith(`#/${project.name}`); }); }) }else{ singleSpa.registerApplication(project.name,()=>System.import(project.name),(location)=>{ return project.base?true:location.hash.startsWith(`#/${project.name}`); }); } }); singleSpa.start(); }) })() </script> </body> </html>
實際渲染出來是
4.5 優化打包配置
因為三個子項目都用到了相同的一部分依賴,可以考慮將公用的依賴不打包進去,改為在主項目主引入來提高打包效率
修改子應用的webpack.base.config.js,增加配置項
在主應用中引入依賴
4.5 增加react應用
同樣是使用webpack打包,不同是包裝器不一樣。其他基本上是一樣的思路即可。
最終demo
很多技術細節寫的很馬虎,就不拿出來獻丑啦~
補充說明:關於應用間通信或者狀態共享的問題,貼一下官方的說明:
通常,我們建議嘗試避免這種情況-將這些應用程序耦合在一起。如果您發現自己經常在應用程序之間執行此操作,則可能要考慮那些單獨的應用程序實際上應該只是一個應用程序。
通常,最好僅針對每個應用程序需要的數據發出API請求,即使其他應用程序已經請求了其中的一部分。實際上,如果您正確地設計了應用程序邊界,最終將只有很少的真正共享的應用程序狀態-例如,您的朋友列表與社交訂閱源具有不同的數據要求。
但是,這並不意味着它無法完成。這里有幾種方法:
1.創建一個共享的API請求庫,該庫可以緩存請求及其響應。如果somone命中某個API,然后另一個應用程序再次命中該API,則它僅使用緩存
2。公開共享狀態作為導出,其他庫可以導入它。可觀察對象(如RxJS)在這里很有用,因為它們可以向訂閱者流式傳輸新值。
3.使用自定義瀏覽器事件進行通信1.使用Cookie,本地/會話存儲或其他類似方法來存儲和讀取該狀態。這些方法最適用於不經常更改的事物,例如登錄的用戶信息。
已經上傳到github,https://github.com/sc1992sc/singleSpa