一、微前端概述
1. 什麼是微前端?
為了解決龐大的一整塊後端服務帶來的變更與擴充套件方面的限制,出現了微服務架構。然而,越來越重的前端工程也面臨同樣的問題,自然地想到了將微服務思想應用(照搬)到前端,於是有了“微前端(micro-frontends)”的概念。即,一種由獨立交付的多個前端應用組成整體的架構風格。具體的,將前端應用分解成一些更小、更簡單的能夠獨立開發、測試、部署的小塊,而在使用者看來仍然是內聚的單個產品。
我們常見後臺專案通常長這樣:
如果我們的專案需要開發某個新的功能,而這個功能另一個專案已經開發好,我們想直接複用時。
說明:我們需要的只是別人專案的這個功能頁面的內容部分,不需要別人專案的頂部導航和選單。
一個比較笨的辦法就是直接把別人專案這個頁面的程式碼拷貝過來,但是萬一別人不是 vue 開發的,或者說 vue 版本、UI 庫等不同,以及別人的頁面載入之前操作(路由攔截,鑑權等)我們都需要拷貝過來,更重要的問題是,別人程式碼有更新,我們如何做到同步更新。甚至當別的專案採用其它技術棧時,如何整合?顯然複製程式碼是行不通的。
以前端元件的概念作類比,我們可以把每個被拆分出的子應用看作是一個應用級元件,每個應用級元件專門實現某個特定的業務功能(如商品管理、訂單管理等)。這裡實際上談到了微前端拆分的原則:即以業務功能為基本單元。經過拆分後,整個系統的結構也發生了變化:
如上圖所示,左側是傳統大型單頁應用的前端架構,所有模組都在一個應用內,由應用本身負責路由管理,是應用分發路由的方式;而右側是基座模式下的系統架構,各個子應用互不相關,單獨執行在不同的服務上,由基座應用根據路由選擇載入哪個應用到頁面內,是路由分發應用的方式。這種方式使得各個模組的耦合性大大降低,而微前端需要解決的主要問題就是如何拆分和組織這些子應用。
典型的基於vue-router的Vue應用與這種架構存在著很大的相似性:
2. 巨無霸專案的存在的問題
- 程式碼越來越多,打包越來越慢,部署升級麻煩,一些外掛的升級和公共元件的修改需要考慮的更多,很容易牽一髮而動全身。
- 專案太大,參與人員越多,程式碼規範比較難管理,程式碼衝突也頻繁。
- 產品功能齊全,但是客戶往往只需要其中的部分功能。剝離不需要的程式碼後,需要獨立制定版本,獨立維護,增加人力成本。
微前端的誕生也是為了解決以上問題:
- 複用(嵌入)別人的專案頁面,但是別人的專案執行在他自己的環境之上。
- 巨無霸應用拆分成一個個的小專案,這些小專案獨立開發部署,又可以自由組合進行售賣。
使用微前端的好處:
- 技術棧無關,各個子專案可以自由選擇框架,可以自己制定開發規範。
- 快速打包,獨立部署,互不影響,升級簡單。
- 可以很方便的複用已有的功能模組,避免重複開發。
二、常見微前端方案
目前主流的微前端方案包括以下幾個:
- iframe
- 基座模式,主要基於路由分發,qiankun和single-spa就是基於這種模式
- 組合式整合,即單獨構建元件,按需載入,類似npm包的形式
- EMP,主要基於Webpack5 Module Federation
- Web Components
iframe:是傳統的微前端解決方案,基於iframe標籤實現,技術難度低,隔離性和相容性很好,但是效能和使用體驗比較差,多用於整合第三方系統;
基座模式:主要基於路由分發,即由一個基座應用來監聽路由,並按照路由規則來載入不同的應用,以實現應用間解耦;
組合式整合:把元件單獨打包和釋出,然後在構建或執行時組合。
EMP:基於Webpack5 Module Federation,一種去中心化的微前端實現方案,它不僅能很好地隔離應用,還可以輕鬆實現應用間的資源共享和通訊。
Web Components:是官方提出的元件化方案,它通過對元件進行更高程度的封裝,來實現微前端,但是目前相容性不夠好,尚未普及。
總的來說,iframe主要用於簡單並且效能要求不高的第三方系統;組合式整合目前主要用於前端元件化,而不是微前端;基座模式、EMP和Web Components是目前主流的微前端方案。
目前微前端最常用的有兩種解決方案:iframe 方案和 基座模式方案。
1. iframe方案
iframe 大家都很熟悉,使用簡單方便,提供天然的 js/css 隔離,也帶來了資料傳輸的不便,一些資料無法共享(主要是本地儲存、全域性變數和公共外掛),兩個專案不同源(跨域)情況下資料傳輸需要依賴 postMessage 。
iframe 有很多坑,但是大多都有解決的辦法:
1. 頁面載入問題
iframe 和主頁面共享連線池,而瀏覽器對相同域的連線有限制,所以會影響頁面的並行載入,阻塞 onload 事件。每次點選都需要重新載入,雖然可以採用 display:none 來做快取,但是頁面快取過多會導致電腦卡頓。(無法解決)
2. 佈局問題
iframe 必須給一個指定的高度,否則會塌陷。
解決辦法:子專案實時計算高度並通過 postMessage 傳送給主頁面,主頁面動態設定 iframe 高度。有些情況會出現多個滾動條,使用者體驗不佳。
3. 彈窗及遮罩層問題
彈窗只能在 iframe 範圍內垂直水平居中,沒法在整個頁面垂直水平居中。
解決辦法1:通過與框架頁面訊息同步解決,將彈窗訊息傳送給主頁面,主頁面來彈窗,對原專案改動大且影響原專案的使用。
解決辦法2:修改彈窗的樣式:隱藏遮罩層,修改彈窗的位置。
4. iframe 內的 div 無法全屏
彈窗的全屏,指的是在瀏覽器可視區全屏。這個全屏指的是佔滿使用者螢幕。
全屏方案,原生方法使用的是 Element.requestFullscreen(),外掛:vue-fullscreen。當頁面在 iframe 裡面時,全屏會報錯,且 dom 結構錯亂。
解決方案:iframe 標籤設定 allow="fullscreen" 屬性即可
5. 瀏覽器前進/後退問題
iframe 和主頁面共用一個瀏覽歷史,iframe 會影響頁面的前進後退。大部分時候正常,iframe 多次重定向則會導致瀏覽器的前進後退功能無法正常使用。並且 iframe 頁面重新整理會重置(比如說從列表頁跳轉到詳情頁,然後重新整理,會返回到列表頁),因為瀏覽器的位址列沒有變化,iframe 的 src 也沒有變化。
6. iframe 載入失敗的情況不好處理
非同源的 iframe 在火狐及 chorme 都不支援 onerror 事件。
解決辦法1:onload 事件裡面判斷頁面的標題,是否 404 或者 500
解決辦法2:使用 try catch 解決此問題,嘗試獲取 contentDocument 時將丟擲異常。
2. 基座模式方案
基座模式方案以single-spa和qiankun為代表,這裡我選擇qiankun。
qiankun 是螞蟻金服開源的一款框架,它是基於 single-spa 的。他在 single-spa 的基礎上,實現了開箱即用,除一些必要的修改外,子專案只需要做很少的改動,就能很容易的接入。
qiankun框架官網:https://qiankun.umijs.org/zh/。
微前端中子專案的入口檔案常見的有兩種方式:JS entry 和 HTML entry。純 single-spa 採用的是 JS entry,而 qiankun 既支援 JS entry,又支援 HTML entry。
JS entry 的要求比較苛刻:
(1)將 css 打包到 js 裡面
(2)去掉 chunk-vendors.js,
(3)去掉檔名的 hash 值
(4)將 single-spa 模式的入口檔案( app.js )放置到 index.html 目錄,其他檔案不變,原因是要擷取 app.js 的路徑作為 publicPath。
建議使用 HTML entry ,使用起來和 iframe 一樣簡單,但是使用者體驗比 iframe 強很多。qiankun 請求到子專案的 index.html 之後,會先用正則匹配到其中的 js/css 相關標籤,然後替換掉,它需要自己載入 js 並執行,然後去掉 html/head/body 等標籤,剩下的內容原樣插入到子專案的容器中 。
二、微前端方案實踐
以“大資料分析”專案為例,將客戶特有的需求,如“電子路單”、“資料填報”單獨提取為可獨立執行的子專案。
大資料分析專案改造為主應用基座,程式碼倉庫地址:http://192.168.1.102/zouqiongjun/big-data-web.git。
客戶自定義需求單獨作為子應用專案,程式碼倉庫地址:http://192.168.1.102/zouqiongjun/zibo-custom-web.git。
1. 各應用工程程式碼結構
sass-base-web:主倉庫,主要存放一些批量操作的指令碼,用於聚合管理倉庫和一鍵編譯、一鍵部署。
倉庫程式碼結構如下圖所示:
big-data-web:大資料分析主應用
zibo-custom-web:客戶自定義需求,微應用倉庫
子應用可以獨立執行,但是當前子應用是直接巢狀在主應用的main內容區域,所以暫時並沒有單獨提供左側選單導航,後續如有需要可以擴充套件和補充此功能。
2. 主應用big-data-web改造
將普通的專案改造成 qiankun 主應用基座,需要進行三步操作:
(1) 建立微應用容器 - 用於承載微應用,渲染顯示微應用;
(2) 註冊微應用 - 設定微應用啟用條件,微應用地址等等;
(3) 啟動 qiankun;
注意:由於big-data-web主應用的路由採用的是hash模式,所以子應用的路由也應該採用hash模式。
1.1 安裝 qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
1.2. 在主應用中註冊微應用
為了使用keepAlive快取,這裡我們採用手動載入微應用的方式。
當微應用資訊註冊完之後,一旦瀏覽器的 url 發生變化,便會自動觸發 qiankun 的匹配邏輯,所有 activeRule 規則匹配上的微應用就會被插入到指定的 container 中,同時依次呼叫微應用暴露出的生命週期鉤子。
在views目錄下,新建AppVueHash.vue,作為子應用的容器,程式碼如下:
<template> <div class="zibo-custom-web"> <div id="zibo-custom-web" class="app-view-box"></div> </div> </template> <script> export default {}; </script> <style lang="scss" scoped> .zibo-custom-web{ position: relative; } </style>
這個id屬性要唯一,最終子應用的內容將會掛載在這裡。
ContainerOther.vue程式碼改造:
<!-- 主體檢視層 --> <div class="avue-view-contain" v-show="!isSearch"> <keep-alive> <router-view class="avue-view keep-alive" v-if="$route.meta.keepAlive && isActiveRoute" v-show="!showAppVueHash" /> </keep-alive> <router-view class="avue-view" v-if="!$route.meta.keepAlive && isActiveRoute" v-show="!showAppVueHash" /> <AppVueHash v-show="showAppVueHash" /> </div>
js程式碼:
import router from "@/router/router"; import store from "@/store"; import AppVueHash from "@/views/AppVueHash.vue"; import { loadMicroApp } from "qiankun"; //子專案路由字首 const isChildRoute = path => website.childRoute.some(item => path.startsWith(item)); const apps = [ { name: "/zibo-custom-web", entry: window.configs.VUE_APP_ZIBO_CUSTOM_URL, container: "#zibo-custom-web", props: { data: { store, router } }, sandbox: { strictStyleIsolation: true // 開啟樣式隔離 } } ]; //控制微應用手動載入 ctrlMicroApp(path){ if (isChildRoute(path)) { this.showAppVueHash = true; this.$nextTick(() => { //手動載入 if(!this.mounted){ this.loadApps = apps.map(item => loadMicroApp(item)) this.mounted=true; } }); } else { this.showAppVueHash = false; }
這裡的container屬性值,必須和AppVueHash.vue元件中的id值保持一致。
根據url地址判斷是否是子應用,如果是子應用,則手動載入,否則隱藏子應用容器,只載入主應用的router-view。
在ContainerOther第一次載入或路由變化時手動載入微應用:
mounted() { this.init(); setTheme(this.themeName); this.ctrlMicroApp(this.$route.path) }, watch: { $route(val) { this.ctrlMicroApp(val.path); }, tagList(newVal,oldVal){ let starts=''; const childRoute = website.childRoute; childRoute.forEach((n,i)=>{ if(i<childRoute.length-1){ starts+=`^${n}|`; }else{ starts+=`^${n}`; } }) const patt = new RegExp(`${starts}`); //之前存在子應用頁籤 const before = oldVal.some(item=>{ return patt.test(item.value); }); //現在存在子應用頁籤 const now = newVal.some(item=>{ return patt.test(item.value); }); if(before && !now){ this.mounted=false; this.loadApps.forEach(app=>{ app.unmount(); }) } }
監聽tab頁籤變化,關閉頁籤時,需要解除安裝子應用。
3. qiankun 子專案zibo-custom-web
- 在 src 目錄新增檔案 public-path.js:
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 修改 index.html 中專案初始化的容器,不要使用 #app ,避免與其他的專案衝突,建議小駝峰寫法
<div id="appVueHash"></div>
- 修改入口檔案 main.js:
// -----------子應用微前端start------------------- let router = null; let instance = null; function render({ data = {} , container } = {}) { router = new VueRouter({ routes, }); instance = new Vue({ router, store, data(){ return { parentRouter: data.router, parentVuex: data.store, } }, render: h => h(App), }).$mount(container ? container.querySelector('#appVueHash') : '#appVueHash'); } if (!window.__POWERED_BY_QIANKUN__) { render(); } //測試全域性變數汙染 console.log('window.a',window.a) export async function bootstrap() { console.log('vue app bootstraped'); } export async function mount(props) { console.log('props from main framework', props.data); render(props); } export async function unmount() { instance.$destroy(); instance.$el.innerHTML = ""; instance = null; router = null; } // -----------子應用微前端end-------------------
主要改動是:引入修改 publicPath 的檔案和 export 三個生命週期。
注意:
- webpack 的 publicPath 值只能在入口檔案修改,之所以單獨寫到一個檔案並在入口檔案最開始引入,是因為這樣做可以讓下面所有的程式碼都能使用這個。
- 路由檔案需要 export 路由資料,而不是例項化的路由物件,路由的鉤子函式也需要移到入口檔案。
- 在 mount 生命週期,可以拿到父專案傳遞過來的資料,router 用於跳轉到主專案/其他子專案的路由,store 是父專案的例項化的 Vuex(也可以傳遞其他資料過來)。
- 修改打包配置 vue.config.js:
const { name } = require("./package"); module.exports = { outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web", devServer: { port: 9010, // 關閉主機檢查,使微應用可以被 fetch disableHostCheck: true, // 配置跨域請求頭,解決開發環境的跨域問題 headers: { "Access-Control-Allow-Origin": "*", }, proxy: { "/api": { //本地服務介面地址 target: "http://192.168.10.112:10067", //cas ws: true, pathRewrite: { "^/api": "/", }, }, }, }, // 以下配置可以修復一些字型檔案載入路徑問題 chainWebpack: (config) => { //忽略的打包檔案 config.externals({ vue: "Vue", "vue-router": "VueRouter", vuex: "Vuex", axios: "axios", "element-ui": "ELEMENT", }); config .plugin('html') .tap(args => { args[0].name = name; return args }); config.module .rule("fonts") .test(/.(ttf|otf|eot|woff|woff2)$/) .use("url-loader") .loader("url-loader") .tap((options) => ({ name: "/fonts/[name].[hash:8].[ext]" })) .end(); }, // 自定義webpack配置 configureWebpack: { output: { library: `${name}-[name]`, // 微應用的包名,這裡與主應用中註冊的微應用名稱一致 libraryTarget: "umd", // 把子應用打包成 umd 庫格式 jsonpFunction: `webpackJsonp_${name}`, //webpack打包之後儲存在window中的key,各個子應用不一致 }, }, };
這裡主要就兩個配置,一個是允許跨域,另一個是打包成 umd 格式。為什麼要打包成 umd 格式呢?是為了讓 qiankun 拿到其 export 的生命週期函式。
注意: 這個 name 預設從 package.json 獲取,可以自定義,只要和父專案註冊時的 name 保持一致即可。
vue.config.js中
outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web",
這裡我子應用專案編譯後會將打包檔案打包到sass-base-web專案中的zibo-custom-web下。
- 路由動態載入
需要給子專案所有的路由都新增一個字首,子專案的路由跳轉如果之前使用的是 path 也需要修改,用 name 跳轉則不用。
avue-router.js:
const oRouter = { path: "/zibo-custom-web", name: "RouterView", component(resolve) { require(["@/components/RouterView.vue"], resolve); }, children: [ { path: path, component(resolve) { require([`../${component}.vue`], resolve); }, name: name, meta: meta, }, ], }; aRouter.push(oRouter);
4. 狀態管理,主應用和微應用之間的通訊
qiankun 通過 initGlobalState: 定義全域性狀態,並返回通訊方法,建議在主應用使用,微應用通過 props 獲取通訊方法;
onGlobalStateChange: 在當前應用監聽全域性狀態,有變更觸發 callback;
setGlobalState: 按一級屬性設定全域性狀態,微應用中只能修改已存在的一級屬性; 換句話說只能修改主用於預先定義的屬性,後面新增的屬性無效。
官方例子:釋出-訂閱的設計模式:
主應用:
import { initGlobalState, MicroAppStateActions } from 'qiankun'; // 初始化 state const actions: MicroAppStateActions = initGlobalState(state); actions.onGlobalStateChange((state, prev) => { // state: 變更後的狀態; prev 變更前的狀態 console.log(state, prev); }); actions.setGlobalState(state); actions.offGlobalStateChange();
子應用:
// 從生命週期 mount 中獲取通訊方法,使用方式和 master 一致 export function mount(props) { props.onGlobalStateChange((state, prev) => { // state: 變更後的狀態; prev 變更前的狀態 console.log(state, prev); }); props.setGlobalState(state); }
如果主應用和子應用都是vue技術棧,可以在子應用中把資料傳遞給子應用,例如store。
props: { data: { store, router } },
5. 各應用之間的獨立倉庫以及聚合管理
實際開發中專案儲存在公司倉庫中,以 gitLab 為例, 當子應用一多,全部放在一個倉庫下面, 這時候就顯得很臃腫了,也很龐大,大大的增加了維護成本,和開發效率;
我們可以通過 sh 指令碼, 初始只需要克隆主倉庫程式碼, 然後通過 sh 指令碼去一鍵拉取所有子應用程式碼。
這裡我將單獨建立一個用來編譯和打包的主倉庫專案sass-base-web,倉庫地址:http://192.168.1.102/zouqiongjun/sass-base-web.git。
專案根目錄下,新建 script/clone-all.sh 檔案,內容如下:
# 子服務 gitLab 地址 SUB_SERVICE_GIT=('http://192.168.1.102/zouqiongjun/big-data-web.git' 'http://192.168.1.102/zouqiongjun/zibo-custom-web.git') SUB_SERVICE_NAME=('big-data-web' 'zibo-custom-web') # 子服務 if [ ! -d "sub-service" ]; then echo '建立sub-service目錄...' mkdir sub-service fi echo '進入sub-service目錄...' cd sub-service # 遍歷克隆微服務 for i in ${!SUB_SERVICE_NAME[@]} do if [ ! -d ${SUB_SERVICE_NAME[$i]} ]; then echo '克隆微服務專案'${SUB_SERVICE_NAME[$i]} git clone ${SUB_SERVICE_GIT[$i]} fi done echo '指令碼結束...' # 克隆完成
當我們啟動主專案的時候,如果想要使用所有子應用的功能,子應用,需要一個一個啟動,這樣無論是開發還是編譯都十分不便。
考慮到國內使用npm裝包可能會由於網路的原因安裝失敗,可以讓npm使用國內淘寶映象。
執行命令:npm config set registry https://registry.npm.taobao.org。
在這個主倉庫專案裡面,我們只需要安裝一個npm-run-all外掛。
執行:yarn add npm-run-all -D或者npm i -D。
該專案下只有一個package.json檔案,用於配置編譯和打包命令,程式碼如下:
{ "name": "sass-big-data-web", "version": "1.0.0", "description": "`qiankun`來實現`vue`技術棧的前端微服務", "main": "index.js", "scripts": { "clone:all": "bash ./scripts/clone-all.sh", "install:zibo": "cd ./sub-service/zibo-custom-web && npm install", "install:main": "cd ./sub-service/big-data-web && npm install", "install-all": "npm-run-all install:*", "start:zibo": "cd ./sub-service/zibo-custom-web && npm run serve ",", "start:main": "cd ./sub-service/big-data-web && npm run serve", "start-all": "npm-run-all --parallel start:*", "serve-all": "npm-run-all --parallel start:*", "build:zibo": "cd ./sub-service/zibo-custom-web && npm run build", "build:main": "cd ./sub-service/big-data-web && npm run build", "build-all": "npm-run-all --parallel build:*" }, "repository": { "type": "git", "url": "http://192.168.1.102/zouqiongjun/sass-big-data-web.git" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "npm-run-all": "^4.1.5" } }
要執行yarn clone:all,需要用到bash,跳轉到sass-base-web目錄,右鍵滑鼠開啟Git bash視窗,如下圖所示:
這樣當我們把各個程式碼倉庫的程式碼都拉取到sub-service這個目錄下面來,如下圖所示:
程式碼拉取完成後, 緊接著就是下載各個專案的依賴及執行。
執行npm run serve-all則可以自動執行package.json中配置的命令,這個命令最終會執行以下三個執行命令:
"start:zibo": "cd ../zibo-custom-web && npm run serve ", "start:main": "cd ../big-data-web && npm run serve",
這樣就不需要我們自己一個一個單獨的去執行各個專案了。
總體執行步驟: 第一步 clone 主應用, 然後依次執行 yarn clone:all --> yarn install-all --> yarn start-all 即可執行整個專案。
build-all:可以編譯整個專案。
sub-service目錄,將其新增到.gitignore當中,因為在sass-base-web專案當中,我們只需要配置和編譯及打包用,並不需要真正的將所有子應用的程式碼都提交到sass-base-web專案中,各子應用都有自己私有的倉庫。
6. 子專案開發的一些注意事項
(1)所有的資源(圖片/音視訊等)都應該放到 src 目錄,不要放在 public 或者static。資源放 src 目錄,會經過 webpack 處理,能統一注入 publicPath。否則在主專案中會404。
(2)避免 css 汙染。元件內樣式的 css-scoped 是必須的。
(3)給 body 、 document 等繫結的事件,請在 unmount 週期清除
(4)謹慎使用 position:fixed
在父專案中,這個定位未必準確,應儘量避免使用,確有相對於瀏覽器視窗定位需求,可以用 position: sticky,但是會有相容性問題(IE不支援)。
常見問題見官網:https://qiankun.umijs.org/zh/faq
7. 部署
1. 常規部署
主應用和微應用都是獨立開發和部署,即它們都屬於不同的倉庫和服務。
場景:主應用和微應用部署到同一個伺服器(同一個IP和埠)
如果伺服器數量有限,或不能跨域等原因需要把主應用和微應用部署到一起。通常的做法是主應用部署在一級目錄,微應用部署在二/三級目錄。
若微應用想部署在非根目錄,在微應用打包之前需要做兩件事:
- 必須配置 webpack 構建時的 publicPath 為目錄名稱,更多資訊請看 webpack 官方說明 和 vue-cli3 的官方說明。
- history 路由的微應用需要設定 base ,值為目錄名稱,用於獨立訪問時使用。
部署之後注意三點:
- activeRule 不能和微應用的真實訪問路徑一樣,否則在主應用頁面重新整理會直接變成微應用頁面。
- 微應用的真實訪問路徑就是微應用的 entry,entry 可以為相對路徑。
- 微應用的 entry 路徑最後面的 / 不可省略,否則 publicPath 會設定錯誤,例如子項的訪問路徑是 http://localhost:8080/app1,那麼 entry 就是 http://localhost:8080/app1/。
通過配置 nginx 埠到目錄的轉發。須要對外開放子利用對應的埠,將編譯好的利用檔案放到對應的配置目錄。
跳轉到sass-base-web目錄,執行npm run build-all,會自動執行所有build:開頭的命令:
"build:zibo": "cd ../zibo-custom-web && npm run build", "build:control": "cd ../control-center && npm run build", "build:main": "cd ../big-data-web && npm run build", "build-all": "npm-run-all --parallel build:*"
zibo-custom-web目錄結構如下圖所示:
這裡是子應用和主應用部署在同一臺伺服器上,且IP和埠相同,nginx不需要額外設定。
如果子應用和主應用部署在同一臺伺服器上, 但是埠不同,需要修改vue.config.js中的outputDir,這個是打包編譯後程式碼存放的路徑,這個不做配置,預設會將程式碼編譯打包到當前根目錄下,並生成一個dist目錄用於存放編譯後的程式碼,如下圖所示:
修改nginx.conf配置:
#gzip on; upstream gateway { server 192.168.31.136:32067;} # 主應用 server { listen 32043; server_name web; root /dist; # 關閉埠重定向 # port_in_redirect off; #charset koi8-r; access_log /var/log/nginx/nginx.log; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ^~/api/ { proxy_read_timeout 600s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffering off; rewrite ^/api/(.*)$ /$1 break; proxy_pass http://gateway; } } # 子應用 server { listen 9010; server_name cus_web; # 子應用編譯後的程式碼路徑 root /zibo-custom-web; # 允許跨域 add_header Access-Control-Allow-Origin *; # 關閉埠重定向 # port_in_redirect off; # charset koi8-r; access_log /var/log/nginx/nginx.log; location ^~/zibo-custom-web/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ^~/api/ { proxy_read_timeout 600s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffering off; rewrite ^/api/(.*)$ /$1 break; proxy_pass http://gateway; } }
2. docker nginx 配置
此處 nginx 主要作用是用於埠目錄轉發,並配置主應用訪問子應用的跨域問題。
使用 docker 配置部署 nginx:
# docker-compose.yml version: '3.1' services: nginx: restart: always image: nginx container_name: nginx ports: - 8888:80 - 8889:8889 - 7100:7100 - 7101:7101 volumes: - /app/volumes/nginx/nginx.conf:/etc/nginx/nginx.conf - /app/volumes/nginx/html:/usr/share/nginx/html - /app/micro/portal:/app/micro/portal - /app/micro/app1:/app/micro/app1 - /app/micro/app2:/app/micro/app2
將編譯後的主應用以及子應用放到對應的資料卷掛載目錄即可,如主應用 /app/micro/portal。
同理,也需要將配置好的 nginx.conf 檔案放到指定的資料卷掛載目錄,使用 docker-compose up -d 啟動即可。
nginx 埠目錄轉發配置:
# nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024;} http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; server { listen 8889; server_name 192.168.2.192; location / { root /app/micro/portal; index index.html; try_files $uri $uri/ /index.html; } } server { listen 7100; server_name 192.168.2.192; # 配置跨域訪問,此處是萬用字元,嚴格生產環境的話可以指定為主應用 192.168.2.192:8889 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; location / { root /app/micro/app1; index index.html; try_files $uri $uri/ /index.html; } } server { listen 7101; server_name 192.168.2.192; # 配置跨域訪問,此處是萬用字元,嚴格生產環境的話可以指定為主應用 192.168.2.192:8889 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; location / { root /app/micro/app2; index index.html; try_files $uri $uri/ /index.html; } }}
部署到生產,需要修改big-data-web/public/util/config.js中的VUE_APP_ZIBO_CUSTOM_URL配置項:
(function() { window.configs = { VUE_APP_CASLOGINURL: "http://192.168.1.99:32080/cas", //cas登入地址 VUE_APP_REDIRECTURL: "http://192.168.51.61:8888/big-data/", //前端部署地址 VUE_APP_SOCKET: "ws://192.168.31.136:32061", //websocket地址' VUE_APP_AMAPURLPREFIX: "https://webapi.amap.com", //高德地圖地址 VUE_APP_ZIBO_CUSTOM_URL:"http://localhost:9010",//自定義微應用地址 //================================================ VUE_APP_AMAPKEY: "xxxxxx" //高德key }; })();
這裡config.js是為了配置檔案外接,不需要編譯。
8. 總結
儘管qiankun框架支援各個子應用使用不同的技術框架,但是都需要子應用做相應的改造,而且在其它技術棧中總是會時不時的出現各種難以預料的錯誤,一旦出現問題,都需要我們去改造程式碼。所以如果都是vue技術棧,建議使用qiankun做微前端。
如果是第三方公司的專案接入進來,由於他們的程式碼不受我們控制,需要酌情考慮是否用iframe。
參考文獻:qiankun 微前端方案實踐及總結