我心目中的微前端
大廠基本在使用的
基於經驗 自己的考慮(以 Vue 項目 爲例子)
前言: 雖然不想搞輪子 ,但是 要吹牛,不搞點輪子 也不好意思得
1.路由加載
- 異步加載 預加載 路由全局合併
- 是否是子路由 ,是否加載完畢 ,加載(loading ,error ,success)
- 路由緩存
目前兩種方案 ,
第一種:使用 system.js 對 子項目進行 加載umd 對象 然後addRouter
第二種:公用全局變量 進行註冊 保存,(通過xhr 請求JS)
Vue-Route https://router.vuejs.org/zh/api/#router-addroutes
注意 坑 ,刷新的情況
1. 使用 system.js 進行模塊加載
let { default: webApp } = await System.import(url)
2. 使用 router.addRouter 進行路由加載
router.addRouter(webApp.router)
3. 問題:怎麼處理 loading 狀態,怎麼更新路由設置
解決方法: 和我們經常遇見的 路由權限一樣 只需要 重置 路由 進行添加
// 重置路由
const createRouter = ()=>{
return new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
}
const router = createRouter();
router.Qclear =()=>{
router.matcher =createRouter().matcher;
};
//路由監聽 子項目處理的清空,subPro 是子項目 加載前的loading 模塊 ,類似與打開小程序下載資源前的的 loading 狀態
async routeEvent(router,subPro){
let {subWebs} = this.data,$t = this;
//更新路由
const updateRoute = async (subWebs)=>{
router.Qclear();//清空路由
let newRoute = [];
subWebs.forEach(curr=>{
if(curr.load) {
newRoute = newRoute.concat(curr.webApp.routes)
}else{
newRoute.push({ path: "/"+curr.path, name: curr.path, component:subPro})
}
});
router.addRoutes(newRoute,{replace:true});
}
//處理初始化路由
await updateRoute(subWebs);
//監聽路由變化 判斷 是否進行加載
router.beforeEach((to, from, next) => {
let {subWebs} = $t.data,index = -1;
subWebs.forEach((curr,cindex)=>{
if(curr.path == to.name)index = cindex;
});
next()
if(index == -1){return}
(async ()=>{
let curr = subWebs[index];
//判斷是否加載
if(!curr.load){
//延時 看loading 效果
await new Promise(resolve => {
setTimeout(()=>{
resolve();
},3000)
})
//加載 -- 這裏 就有思考了,(服務端渲染的加載情況 ,是否引入第三方加載工具,System.js 還是自己寫)
// - 服務端渲染的情況 是否是在服務端進行加載 /還是在客戶端進行loading 加載
// - 暫做 在客戶端進行加載的情況 ,服務端 有空再搞
let { System } = window;
// eslint-disable-next-line no-unused-vars
let { default: webApp } = await System.import(curr.form);
curr.load = true;
curr.webApp = webApp;
subWebs[index] = curr;
$t.data.subWebs = subWebs;
await updateRoute(subWebs);
}
next(to.path);//當前的導航被中斷,然後進行一個新的導航
})()
})
},
基本第一步 就完了 看看:展示狀態
緩存路由:是使用 Qcenter 種的全局變量進行 保存
//配置 處理
data: {
//保存主路由的基本信息
main: {},
//子項目配置
subWebs:[
//load:===是否加載 webApp 異步路由保存在緩存裏面
{path:'web',desc:"子項目-01",form:'/web/main.js',port:"8081",load:false,webApp:{}}
]
},
待優化:
- 某些模塊 可以預先加載 ,減少 loading時間
- 這樣寫 每次更新路由的時候都會進行 重置 ,暫時沒有想到更好的方式
- 子項目 加載loading 可以配置 不同的狀態,類似骨架屏
- 對於SSR項目處理 沒有進行單獨配置
小結總結:基本功能雖然實現了,但是仍舊需要 多考慮 多想想, 項目的配置 怎麼才能更簡單,怎麼才能更高效
2.數據同步
- 子項目 和主項目 數據關聯關係
- 子項目 之間的 數據關聯關係
代碼世界:有句話,很好,😂約定大於配置😂 -----簡單而言 就是 遵循 某些規範 儘量少BUG
原則上: 各個模塊大部分數據都是獨立的 ,很少是需要公用的(常見的 token, 登陸用戶信息,配置信息)
Vuex 的 store.registerModule 註冊子模塊的所有model
所有子項目 的模塊,都註冊到主項目上面 ,進行統一處理 命名規則 爲 子項目名稱
主項目 獲取子項目 種的值 (有未加載的情況 需要進行判斷)
子項目獲取 主項目的 值
1. 問題:由於子項目的store 註冊到 主項目,在子項目 種獲取值 就有區別,原因是公用了 store
解: 約定 ,在子項目 devClient 開發時 主動創建根目錄 ,取值的時候 都在 創建的目錄下,打包時 和 dev 時 去掉 根目錄,然後 進行取值(沒辦法 ,暫時想不到其他方法了)
注: 子項目 store 的取值問題需要約定
3.變量隔離 (數據變量,樣式變量)
- 基本約定 (那些變量 不能 使用 設置關鍵字 關鍵詞 列入 windows 中綁定 相同變量)
- 咋處理呢,qiankun 的做法是 用哪個加載哪個,不用了就卸載掉,這樣就完成了隔離
- 數據隔離:由於數據基本都在 Vuex store 全局使用一個主 Store 所以 ,基本沒啥問題
- 樣式隔離::開發規範(如BEM)、CSS 預處理(如SASS)、模塊定義(如CSS Module)、用 JS 來寫(CSS-in-JS)、以及shadow DOM特性
- 參考 http://www.ayqy.net/blog/micro-frontends/
4.事件消息
- 子路由 之間的消息處理
- 子路由與 主路由消息 處理
由於項目的都是基於 Vue 的所以使用 EventBus 就可以了,全局公用一個BUS,都是走公共方法裏面提取的哈(前提是)
1. 創建文件 bus.js
import Vue from 'vue';
export default new Vue();
2. 引用js 綁定事件 觸發事件
import Bus from '~/commons/bus.js';
//綁定事件
Bus.$on('showTips', target => {
...
console.log(target);
...
});
//觸發事件
Bus.$emit('showTips', data);
5.公共 方法的 提取暴露
- 是個問題 主要是本地開發 和 本地協同開發的區別
- 那些是公共的 東西(Bus,request, memory) 打包合併 js
- 本地開發:遠程引入JS ()
6. 環境配置
之前就很多地方都說了 不同環境,再在這裏統一說一下
子項目
package.json
"scripts": {
"dev": "vue-cli-service serve --mode development",// 本地協同 開發環境
"devClient": "vue-cli-service serve --mode devClient",// 本地 單獨開發環境
"build": "vue-cli-service build --mode production"
"lib": "vue-cli-service build --target lib --name index src/main.js" //正式打包
},