示例代碼倉庫:
yl-qiankun-base:https://gitee.com/dongche/yl-qiankun-base.git
yl-qiankun-child-vue:https://gitee.com/dongche/yl-qiankun-child-vue.git
微前端概念起源
微前端概念最早其實是借鑒了微服務(wù)的概念,最早是出現(xiàn)在2016年的ThoughtWorks Technology Radar(ThoughtWorks技術(shù)雷達(dá))
什么是微前端
MicroFrontends 官方解釋:用來構(gòu)建能夠讓 多個團(tuán)隊 獨(dú)立交付項目代碼的 現(xiàn)代web app 技術(shù),策略以及實踐方法
MicroFrontends 官網(wǎng):https://swearer23.github.io/micro-frontends/
所以微前端并不是一個單純的技術(shù)點(diǎn),而是一個為了解決復(fù)雜應(yīng)用,特別時便于以后維護(hù)的思路和方法。
核心思路:根據(jù)功能模塊拆解應(yīng)用,然后根據(jù)需求組裝,應(yīng)用之間可通信
特點(diǎn): 1.模塊可以使用不同技術(shù)棧
2.應(yīng)用隔離,各應(yīng)用可獨(dú)立部署,可獨(dú)立運(yùn)行
3.應(yīng)用之間松耦合
4.可漸進(jìn)式遷移
用圖做個簡單說明:
擴(kuò)展理解:組件化的延伸,由項目內(nèi)的組件化擴(kuò)展到項目之間的組件化
微前端的應(yīng)用場景
1.老系統(tǒng)的迭代:復(fù)雜的老系統(tǒng),可能由于各種原因,代碼風(fēng)格不一,代碼冗雜,質(zhì)量參差不齊,然后還要不斷加入新功能。
2.復(fù)雜應(yīng)用功能模塊的拆分和細(xì)化,便于各團(tuán)隊同步進(jìn)行開發(fā)和維護(hù)
3.有比較大功能模塊需要在不同的項目中使用
4.需求不定,經(jīng)常有對項目進(jìn)行拆分和融合
5.平臺型應(yīng)用。方便刪減功能模塊和外部接入
目前市面上的主流微前端方案及框架
微前端模式
- 自由組織模式
沒有形式,自由嵌套 - 基座模式
有一個父級主應(yīng)用為容器基座,其他應(yīng)用需要注冊接入 - 去中心模式
各應(yīng)用之間各自為政又可以彼此分享資源,沒有基座不存在主次關(guān)系
微前端方案
-
iframe 方案
- 優(yōu)點(diǎn):html 提供的標(biāo)簽,天然隔離,任意嵌套,能加載任意web應(yīng)用。適合自由組織自由嵌套模式,無需配置,簡單易用
缺點(diǎn):參考qiankun技術(shù)圓桌的 Why Not Iframe
- url 不同步。瀏覽器刷新 iframe url 狀態(tài)丟失、后退前進(jìn)按鈕無法使用。
- UI 不同步,DOM 結(jié)構(gòu)不共享。想象一下屏幕右下角 1/4 的 iframe 里來一個帶遮罩層的彈框,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中..
- 全局上下文完全隔離,內(nèi)存變量不共享。iframe 內(nèi)外系統(tǒng)的通信、數(shù)據(jù)同步等需求,主應(yīng)用的 cookie 要透傳到根域名都不同的子應(yīng)用中實現(xiàn)免登效果
- 慢。每次子應(yīng)用進(jìn)入都是一次瀏覽器上下文重建、資源重新加載的過程。
-
web Components 方案
Web Components 是一套 瀏覽器原生組件,由google發(fā)起。
web Components 三大技術(shù):- Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然后可以在您的用戶界面中按照需要使用它們。
- Shadow DOM(影子DOM):一組JavaScript API,用于將封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現(xiàn))并控制其關(guān)聯(lián)的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔(dān)心與文檔的其他部分發(fā)生沖突。
- HTML templates(HTML模板): <template> 和 <slot> 元素使您可以編寫不在呈現(xiàn)頁面中顯示的標(biāo)記模板。然后它們可以作為自定義元素結(jié)構(gòu)的基礎(chǔ)被多次重用
具備實現(xiàn)微前端的特點(diǎn):
1.技術(shù)棧無關(guān):瀏覽器原生支持,和框架沒有關(guān)系
2.獨(dú)立運(yùn)行,應(yīng)用隔離:Shadow DOM 特性- web Componets組件獨(dú)立開發(fā),相互之間沒有依賴關(guān)聯(lián),但是又可以作為組件自由引入組裝
最大缺點(diǎn):目前瀏覽器兼容性差
參照 can i use
image.png
web Components 偏向基座模式。
目前國內(nèi)京東的 micro-app 借鑒了web components 的思想
-
基座模式的基于single-spa 的路由劫持方案
這是目前主流的微前端方案,single-spa 也是最早成熟的微前端方案,特別適用于單頁面應(yīng)用。single-spa是通過監(jiān)聽 url change 事件,在路由變化時匹配到渲染的子應(yīng)用并進(jìn)行渲染,這個思路也是目前實現(xiàn)微前端的主流方式。同時single-spa要求子應(yīng)用修改渲染邏輯并暴露出三個方法:bootstrap、mount、unmount,分別對應(yīng)初始化、渲染和卸載,這也導(dǎo)致子應(yīng)用需要對入口文件進(jìn)行修改。single-spa 最大問題是配置麻煩
qiankun出自阿里,基于single-spa進(jìn)行封裝,繼承single-spa特性,但是使用簡單,上手相對容易 -
去中心化模式的 ESM 方案
ESM 是 ES Module 的縮寫,是 Ecma script 2015 中提出的一種前端模塊化手段。ESM方案webpack5模塊聯(lián)邦,多個應(yīng)用可以互相嵌套,可以深入到組件導(dǎo)入導(dǎo)出
ESM 方案可視作 ES6 模塊化的擴(kuò)展,使用遠(yuǎn)程加載ESM模塊方式。成熟方案參考:
EMP官網(wǎng)
EMP github
基座模式:主應(yīng)用基于vue及qiankun 的微前端實踐
qiankun 基于 single-spa 封裝,是目前市面上最主流的微前端方案,出自阿里。
qiankun 官網(wǎng):https://qiankun.umijs.org/zh/guide
qinkun 技術(shù)圓桌:https://www.yuque.com/kuitos/gky7yw/nwgk5a
簡單說下qiankun
這里我們不對qiankun做什么深入點(diǎn)剖析,想詳細(xì)了解qiankun的可去官網(wǎng),或者研究下源碼。
我們拿一個做好的微前端項目,用瀏覽器打開控制臺,查看ajax請求,會發(fā)現(xiàn)有個請求子應(yīng)用的ajax請求,這個請求返回了子頁面內(nèi)容
這時候我們點(diǎn)擊能訪問子應(yīng)用頁面的按鈕或菜單,能看到如下內(nèi)容
查看html
簡單來說,qiankun就是拿到子應(yīng)用的訪問路徑(entry配置),并且給每個子應(yīng)用都起一個獨(dú)一無二的名字(name配置),然后通過ajax請求子應(yīng)用的數(shù)據(jù),再解析到主應(yīng)用設(shè)定好的容器里面(比如#micro-app)。至于具體拿到數(shù)據(jù)后是如何進(jìn)行解析,如何劫持路由的,這個就不做深討論,主要是我也不知道。
下面我們來做基于qiankun 的 微前端實踐
項目搭建
按照 qiankun 的說法,接入qiankun時,react,vue等單頁面應(yīng)用使用history模式最好,所以我們這里主應(yīng)用和子應(yīng)用暫時都使用 history模式
- 創(chuàng)建主應(yīng)用
這里的主應(yīng)用我們用vue-ant-design-pro 創(chuàng)建,應(yīng)用名 yl-qiankun-basegit clone --depth=1 https://github.com/vueComponent/ant-design-vue-pro.git yl-qiankun-base
- 創(chuàng)建vue子應(yīng)用,也是基于vue-ant-design-pro 創(chuàng)建,子應(yīng)用名 yl-qiankun-child-vue
git clone --depth=1 https://github.com/vueComponent/ant-design-vue-pro.git yl-qiankun-child-vue
主應(yīng)用改造
下載qiankun
npm i qiankun --save
首先將主應(yīng)用的id="app"改掉,以免和子應(yīng)用沖突,將public/index.html 里面的id="app"改為獨(dú)有的id。這里用 ylQiankunBase
第二,我們這里假設(shè)業(yè)務(wù)確定為希望從菜單加載子應(yīng)用,即點(diǎn)擊菜單才加載子應(yīng)用。那么這里把項目中的 BasicLayout改掉。ant-design-pro 有些東西封裝得比較死,不符合要求的就去掉。
這里首先說下基本思路:主應(yīng)用除了加載子應(yīng)用,還要加載自己本身應(yīng)用里面的路由,所以BasicLayout是不能滿足需求的。除了改造BasicLayout外,我們還要加個裝載子應(yīng)用的容器組件ChildAppLayout。
1)改造BasicLayout
A.重寫B(tài)asicLayout,理由是我們需要在點(diǎn)擊菜單時做更多的事情,而pro-layout沒有提供菜單點(diǎn)擊api,并且a-menu @select 事件 返回的數(shù)據(jù)也不符合要求,我們可能希望得到點(diǎn)擊的整個菜單數(shù)據(jù),而@select事件只是給了key。
B.考慮到菜單組件以后有可能會重用,這里吧菜單組件抽離除了,命名 Slider
改造后的BasicLayout 如下:
<template>
<a-layout id="components-layout-demo-fixed-sider">
<slider/>
<a-layout :style="{ marginLeft: '200px' }">
<a-layout-header :style="{ background: '#fff', padding: 0 }" />
<a-layout-content :style="{ margin: '24px 16px 0', overflow: 'initial' }">
<router-view/>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script>
import Slider from "../components/slider/slider"
export default {
components:{
Slider
},
}
</script>
Slider 菜單組件如下:
<template>
<a-layout-sider :style="{ overflow: 'auto', height: '100vh', position: 'fixed', left: 0 }">
<div class="logo" >
yl-qiankun-base
</div>
<a-menu theme="dark" mode="inline" :default-selected-keys="['4']">
<a-sub-menu v-for='(menu,index) in menus' :key='menu.name'>
<span slot="title"><a-icon :type="menu.meta.icon" /><span>{{ menu.meta.title }}</span></span>
<a-menu-item v-for='(child,index) in menu.children' :key='child.name' @click.native='select(child)'>
<a-icon :type="child.meta.icon" />
<span class="nav-text">{{ child.meta.title }}</span>
</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Slider',
data() {
return {
menus:[],//菜單
}
},
computed: {
...mapState({
// 動態(tài)主路由
mainMenu: state => state.permission.addRouters
}),
},
mounted() {
const routes = this.mainMenu.find(item => item.path === '/')
this.menus = (routes && routes.children) || []
},
methods: {
/**
* 選中菜單
* @param menuInfo
*/
select(menuInfo){
console.log(menuInfo)
const { name } = menuInfo
this.$router.push({
name
})
},
}
}
</script>
<style>
#components-layout-demo-fixed-sider .logo {
height: 32px;
background: rgba(255, 255, 255, 0.2);
margin: 16px;
font-size: 20px;
color: #ffffff;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
</style>
ChildAppLayout.vue,這里我們將注冊微應(yīng)用的代碼抽成函數(shù)registerChildApp放到util中
<template>
<div>
<div id="micro-app"></div>
</div>
</template>
<script>
import Slider from '@/components/slider/slider'
import {registerChildApp} from '@/utils/util'
export default {
components:{
Slider
},
created () {
registerChildApp()
},
}
</script>
在util.js中寫registerChildApp函數(shù)。
這里說下幾個參數(shù):
name: 子應(yīng)用名稱,這個注意必須和 子應(yīng)用package.json里面的name一致,生產(chǎn)環(huán)境打包后,qiankun將會根據(jù)這個name查找子應(yīng)用
entry:子應(yīng)用入口地址。實際使用時本地開發(fā)可使用localhost,生產(chǎn)環(huán)境需要更改為子應(yīng)用的實際訪問路徑,這里可以使用Node環(huán)境變量根據(jù)環(huán)境賦值。在本示例中,子應(yīng)用訪問不僅不是根路徑,是yl-qiankun-child-vue下,因此entry要攜帶/yl-qiankun-child-vue
container: 子應(yīng)用加載容器,全局唯一id值
activeRule: 加載子應(yīng)用的跟路徑。這里確保和子應(yīng)用訪問路徑的yl-qiankun-child-vue一致。否則有坑
import { initGlobalState, registerMicroApps, runAfterFirstMounted, start } from 'qiankun'
import Vue from 'vue'
/**
* 注冊子應(yīng)用
*/
export const registerChildApp = () => {
// // 注冊子應(yīng)用
registerMicroApps(
[
{
name: 'ylQiankunChildVue', // 微應(yīng)用應(yīng)用名稱,同微應(yīng)用的package.json中的name一致
entry: '//localhost:6532/yl-qiankun-child-vue/',
container: '#micro-app', //父級應(yīng)用裝載子應(yīng)用的容器id
activeRule: '/yl-qiankun-child-vue', //加載子應(yīng)用的跟路徑
},
],
{
beforeLoad: [
app => {
console.log('beforeLoad');
}
],
beforeMount: [
app => {
console.log('beforeMount');
}
],
beforeUnmount: [
app => {
console.log('beforeUnmount');
}
],
afterUnmount: [
app => {
console.log('afterUnmount');
}
]
}
)
// setDefaultMountApp(firstApp)
// 第一個子應(yīng)用加載完畢后回調(diào)
runAfterFirstMounted(()=>{
console.log('第一個子應(yīng)用加載完畢后的回調(diào)')
})
// 通訊
// 將action對象綁到Vue原型上,為了項目中其他地方使用方便
Vue.prototype.$qiankunActions = initGlobalState({menuClick:'init',})
start({prefetch :'all'})
}
子應(yīng)用改造
子應(yīng)用不需要安裝qiankun
- 基于vue 的子應(yīng)用改造
注意這里的子應(yīng)用基于 vue-ant-design-pro創(chuàng)建,所以帶有些默認(rèn)的文件。另外子應(yīng)用訪問路徑放在 yl-qiankun-child-vue 下,這里需要配合配置vue.config.js
基于vue的子應(yīng)用的改造主要有:
添加public-path.js 文件;改寫router路由導(dǎo)出文件;權(quán)限文件permission.js;main.js文件;組件容器BasicLayout.vue文件
首先添加public-path.js。在和main.js同級處新建public-path.js,內(nèi)容如下
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
如果這段代碼引發(fā)了你的eslient報錯,請將相關(guān)規(guī)則關(guān)閉,我這邊就將camelcase設(shè)為了off
第二,改造main.js
這里說下 main.js改造的關(guān)鍵地方:
1)頂部引入 public-path.js
2)將路由注冊移植到main.js中。這里影響可能比較大,這樣的話,以前引入router文件的地方,都得改造,比如permission.js
3)增加qiankun 需要導(dǎo)出的三個生命周期函數(shù) bootstrap,mount,unmount
詳細(xì)代碼如下:(這里ant-design-pro創(chuàng)建攜帶的代碼無關(guān)的已忽略刪除)
// with polyfills
import './public-path' // 引入公共路徑
import Vue from 'vue'
import App from './App.vue'
import store from './store/'
import i18n from './locales'
import Router from 'vue-router'
import antBootstrap from './core/bootstrap'
import { constantRouterMap } from '@/config/router.config'
import routerEach from '@/permission'
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
Vue.use(Router)
Vue.config.productionTip = false
let router = null
let instance = null
function render() {
router = new Router({
base: '/yl-qiankun-child-vue/',
routes:[...constantRouterMap],
mode: 'history',
})
routerEach(router) // 路由監(jiān)聽要在 new Vue 之前
instance = new Vue({
store,
router,
i18n,
created: antBootstrap,
render:h=>h(App),
}).$mount('#app')
}
// 生命周期 - 掛載前
export async function bootstrap (props) {
console.log('one bootstrap')
console.log(props)
}
// 生命周期 - 掛載后
export async function mount(props) {
// console.log('one mount');
props.onGlobalStateChange((state,prev)=>{
console.log(state)
})
// 渲染
render()
}
// 生命周期 - 解除掛載
export async function unmount(){
instance.$destroy()
instance.$el.innerHTML = ""
instance = null
router = null
}
// 本地調(diào)試
if(!window.__POWERED_BY_QIANKUN__){
render()
}
關(guān)于base: '/yl-qiankun-child-vue/'這行,如果不使用qiankun和使用qiankun,路徑不一致,則需判斷環(huán)境,可以寫成base: window.POWERED_BY_QIANKUN ? '/yl-qiankun-child-vue/' : '/' 類似
關(guān)于routerEach,由于permission.js 已經(jīng)無法直接引入router文件獲取router對象,這里改為導(dǎo)出函數(shù),傳遞router方式。詳見permission.js改造
第三,改造permission.js(無關(guān)的忽略)
import store from './store'
import storage from 'store'
import NProgress from 'nprogress' // progress bar
import '@/components/NProgress/nprogress.less' // progress bar custom style
import notification from 'ant-design-vue/es/notification'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { i18nRender } from '@/locales'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const allowList = ['login', 'register', 'registerResult'] // no redirect allowList
const loginRoutePath = '/user/login'
const defaultRoutePath = '/list'
const routerEach = (router) => {
router.beforeEach((to, from, next) => {
// 全局路由守衛(wèi)邏輯代碼,太占位置,省略。。。
})
router.afterEach(() => {
NProgress.done() // finish progress bar
})
}
export default routerEach
重點(diǎn)就是將router.beforeEach和afterEach部分抽成一個導(dǎo)出的函數(shù)routerEach,并且傳入router對象參數(shù)
第四,改造vue.config.js
這里改造的重點(diǎn):
配置輸出
config.output.library = name
config.output.libraryTarget = 'umd' // 把微應(yīng)用打包成 umd 庫格式
config.output.jsonpFunction = `webpackJsonp_${name}`
允許跨域
headers: {
'Access-Control-Allow-Origin':'*'
},
完整代碼:
const path = require('path')
const webpack = require('webpack')
const {name} = require('./package') //引入包名
module.exports = {
publicPath:'/yl-qiankun-child-vue/',
configureWebpack: config => {
// preview.pro.loacg.com only do not use in your production;
if (process.env.VUE_APP_PREVIEW === 'true') {
// add `ThemeColorReplacer` plugin to webpack plugins
config.plugins.push(createThemeColorReplacerPlugin())
}
// 為了讓主應(yīng)用能正確識別微應(yīng)用暴露出來的一些信息,微應(yīng)用的打包工具需要增加如下配置
config.output.library = name
config.output.libraryTarget = 'umd' // 把微應(yīng)用打包成 umd 庫格式
config.output.jsonpFunction = `webpackJsonp_${name}`
// if prod, add externals
config.externals = isProd ? assetsCDN.externals : {}
},
devServer: {
port:6532, // 子應(yīng)用的端口最好設(shè)置特別點(diǎn),不容易被覆蓋
// 允許被主應(yīng)用跨域fetch請求到
headers: {
'Access-Control-Allow-Origin':'*'
},
open: false, //配置自動啟動瀏覽器
https: false,
hotOnly: false,
},
}
第五,改造BasicLayout.vue
由于本示例中,子應(yīng)用是點(diǎn)擊菜單加載的,也就是說在微前端模式下運(yùn)行,子應(yīng)用是不需要菜單和頂部的,我們根據(jù)qiankun環(huán)境變量window.POWERED_BY_QIANKUN來做更改。微前端模式下,去掉菜單和頂部,獨(dú)自運(yùn)行時需要菜單和頂部。
改造代碼如下(這里其實改動很小,只需要一個v-if 和 v-else根據(jù)環(huán)境判斷,為了不浪費(fèi)篇幅,其他代碼省略)
<template>
<router-view v-if="isQiankun"/>
<pro-layout
:menus="menus"
:collapsed="collapsed"
:mediaQuery="query"
:isMobile="isMobile"
:handleMediaQuery="handleMediaQuery"
:handleCollapse="handleCollapse"
:i18nRender="i18nRender"
v-bind="settings"
v-else
>
<!--里面代碼省略-->
</pro-layout>
</template>
<script>
//引用及其他代碼均省略,只展示isQiankun變量
export default {
name: 'BasicLayout',
data () {
return {
isQiankun:window.__POWERED_BY_QIANKUN__,// 是否時微前端qiankun環(huán)境
}
},
}
</script>
當(dāng)然,如此改造比較粗糙,你會發(fā)現(xiàn)子應(yīng)用獨(dú)立運(yùn)行和在微應(yīng)用下運(yùn)行時界面表現(xiàn)可能會有些不同。想要保持一致,還是得去掉 pro-layout,自己重構(gòu)這個容器
一些報錯和坑及注意事項及小知識
下面列舉一些實踐過程中可能出現(xiàn)的報錯和坑
- 下面是子應(yīng)用為 vue項目的報錯
1.按照qiankun官網(wǎng)配置 webpack 的 output,報錯 Invalid options in vue.config.js: "output" is not allowed。官網(wǎng)配置示例是這樣的
[圖片上傳失敗...(image-7f071a-1649416618198)]
這是因為比較高版本的webpack已經(jīng)不允許這種寫法,這個配置要放在configureWebpack中
const {name} = require('./package')
module.exports = {
configureWebpack: config=>{
// console.log(config)
config.output.library=name
config.output.libraryTarget= 'umd'
config.output.jsonpFunction=`webpackJsonp_${name}`
},
}
2.Error: single-spa minified message #20
[圖片上傳失敗...(image-b71d38-1649416618198)]
這個問題多半是主應(yīng)用 注冊 子應(yīng)用時,即調(diào)用 registerMicroApps 時第一個參數(shù)穿得不對,對比官方demo檢查下第一個參數(shù)數(shù)據(jù)
3.跨越 Access to fetch at 'http://localhost:6532/' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Uncaught (in promise) TypeError: application 'ylQiankunChildVue' died in status LOADING_SOURCE_CODE: Failed to fetch
ncaught TypeError: application 'ylQiankunChildVue' died in status LOADING_SOURCE_CODE: Failed to fetch
[圖片上傳失敗...(image-8c9ada-1649416618198)]
1)配置子應(yīng)用的時候,要運(yùn)行父級應(yīng)用跨域訪問子應(yīng)用。否則父級應(yīng)用拿不到數(shù)據(jù)無法讀取子應(yīng)用文件。這個在qiankun快速上手部分沒有說,但是在 qiankun官網(wǎng) 項目實戰(zhàn)部分有配置。要加上devServer 配置 headers配置,設(shè)置為 Access-Control-Allow-Origin: '*'
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
};
2)檢查子應(yīng)用是否有public-path文件,main.js里面是否引入pablic-path.js
[圖片上傳失敗...(image-721b16-1649416618198)]
main.js 最頂部,記得要放最頂部
import './public-path' // 引入公共路徑
如果做了兩點(diǎn)配置還有跨越報錯,那就很可能是主應(yīng)用的entry url 設(shè)置不對
4.開發(fā)環(huán)境下,子應(yīng)用反向代理無效
因為在微前端模式下,子應(yīng)用的資源是主應(yīng)用通過http請求獲取后在主應(yīng)用解析的,所以開發(fā)環(huán)境下的反向代理,只有主應(yīng)用的配置生效。如果子應(yīng)用的方向代理配置和主應(yīng)用原本的不一致,需要在主應(yīng)用配置子應(yīng)用需要的代理
5.application 'ylQiankunChildVue' died in status NOT_MOUNTED: [qiankun]: Target container with #micro-app not existed after ylQiankunChildVue mounted!
[圖片上傳失敗...(image-4d11f5-1649416618198)]
這個報錯的意思是注冊的時候,容器 #micro-app 不存在,子應(yīng)用死亡。這個錯誤真的也很常見,造成的原因可能不止一個。反正也很坑。
1)對于主應(yīng)用是vue的項目,等dom加載完成后再注冊,this.$nextTick里面注冊
-
由于vue-cli項目創(chuàng)建的時候,都會有個 id="app",這個是個坑,記得把父級的所有id="app"去掉。否則一直報這個錯誤讓你無解。有些項目并不是直接用vue-cli創(chuàng)建的,而是基于一些做好的布局框架,比如本文的主應(yīng)用就是基于vue-ant-design-pro 創(chuàng)建點(diǎn),這個庫創(chuàng)建的項目在App.vue里面還有個id="app",記得去掉,或者改為和main.js里面使用的一致。否則坑的讓你找不著北
[圖片上傳失敗...(image-c1f435-1649416618198)]
3)上面兩點(diǎn)都檢查過了,那就看看 容器id ,比如本文的 #micro-app 是否存在重復(fù)總的一點(diǎn)就是確保各注冊應(yīng)用id唯一,子應(yīng)用容器id唯一
更多問題解決參考 qiankun 常見問題,里面有很多踩過的坑,會有不少幫助
6.主應(yīng)用和子應(yīng)用使用同一套開發(fā)框架,比如都用基于vue 的element-ui,如何保持主題一致?
答案就是如果主應(yīng)用設(shè)定了自己的主題(非element-ui默認(rèn)主題),那么子應(yīng)用則不需要設(shè)置主題,那么子應(yīng)用就會使用主應(yīng)用的主題樣式。當(dāng)然這個時候如果子應(yīng)用單獨(dú)運(yùn)行,那主題就是element-ui的默認(rèn)主題了,想要單獨(dú)運(yùn)行時不使用默認(rèn)主題,接入微應(yīng)用時使用主應(yīng)用的主題,可以根據(jù)window.POWERED_BY_QIANKUN判斷環(huán)境來加載主題
7.從子項目頁面跳轉(zhuǎn)到主項目自身的頁面時,主項目頁面的 css 未加載
在全局路由守衛(wèi)router.beforeEach處做處理,示例:
/**
* 解決從子項目頁面跳轉(zhuǎn)到主項目自身的頁面時,主項目頁面的 css 未加載的 bug
* 產(chǎn)生這個問題的原因是:在子項目跳轉(zhuǎn)到父項目時,子項目的卸載需要一點(diǎn)點(diǎn)的時間,在這段時間內(nèi),父項目加載了,插入了 css,但是被子項目的 css 沙箱記錄了,然后被移除了。父項目的事件監(jiān)聽也是一樣的,所以需要在子項目卸載完成之后再跳轉(zhuǎn)。
*解決方案:先復(fù)制 HTMLHeadElement.prototype.appendChild 和 window.addEventListener ,路由鉤子函數(shù) beforeEach 中判斷一下,如果當(dāng)前路由是子項目,并且去的路由是父項目的,則還原這兩個對象
*/
const rawAppendChild = HTMLHeadElement.prototype.appendChild
const rawAddEventListener = window.addEventListener
router.beforeEach((to, from, next) => {
if(/^\/yl-qiankun-child-vue\//.test(from.path) && !/^\/yl-qiankun-child-vue\//.test(to.path)){
// 用路徑來判斷是否是從子項目跳轉(zhuǎn)到主項目
HTMLHeadElement.prototype.appendChild = rawAppendChild
window.addEventListener = rawAddEventListener
}
next()
})
你真的需要微前端嗎
最后,還是想談下,我們真的要使用微前端嗎?
個人覺得技術(shù)是要選則最合適自身業(yè)務(wù)的,不是為了使用技術(shù)而選技術(shù),也不是為了追隨潮流而使用技術(shù)。必須得考慮目前業(yè)務(wù),技術(shù)團(tuán)隊情況,以及以后業(yè)務(wù)和技術(shù)團(tuán)隊可能的發(fā)展。拒絕教科書式照搬,還是得實事求是。
參照qiankun技術(shù)圓桌:你可能并不需要微前端中的總結(jié):
滿足以下幾點(diǎn),你可能就不需要微前端
- 你/你的團(tuán)隊 具備系統(tǒng)內(nèi)所有架構(gòu)組件的話語權(quán)
簡單來說就是,系統(tǒng)里的所有組件都是由一個小的團(tuán)隊開發(fā)的。 - 你/你的團(tuán)隊 有足夠動力去治理、改造這個系統(tǒng)中的所有組件
直接改造存量系統(tǒng)的收益大于新老系統(tǒng)混雜帶來的問題。 - 系統(tǒng)及組織架構(gòu)上,各部件之間本身就是強(qiáng)耦合、自洽、不可分離的
系統(tǒng)本身就是一個最小單元的「架構(gòu)量子」,拆分的成本高于治理的成本。 - 極高的產(chǎn)品體驗要求,對任何產(chǎn)品交互上的不一致零容忍
不允許交互上不一致的情況出現(xiàn),這基本上從產(chǎn)品上否決了漸進(jìn)式升級的技術(shù)策略
文章參考:
《對比多種微前端方案》
MicroFrontends