微前端 qiankun 項(xiàng)目實(shí)踐 !!! 防踩坑指南
作者:zxh1307
原文地址:https://juejin.im/post/5ea55417e51d4546e347fda9
點(diǎn)擊上方“前端簡報(bào)”,選擇“設(shè)為星標(biāo)”
第一時(shí)間關(guān)注技術(shù)干貨!
導(dǎo)語
最近在做微前端的項(xiàng)目 , 過程中真是踩了不少坑 , 在有限的資料中不斷試錯(cuò) , 默默無語兩行淚 哈哈. 在此次將采坑部分都記錄下來, 讓更多的人少走點(diǎn)彎路 , 此項(xiàng)目使用 螞蟻金服qiankun 為基礎(chǔ)作為開發(fā) . 話不多說 開講 !!!
那什么是 qiankun 呢
qiankun 是一個(gè)基于 single-spa 的微前端實(shí)現(xiàn)庫,旨在幫助大家能更簡單、無痛的構(gòu)建一個(gè)生產(chǎn)可用微前端架構(gòu)系統(tǒng)。
什么是微前端
微前端架構(gòu)具備以下幾個(gè)核心價(jià)值:
技術(shù)棧無關(guān)
主框架不限制接入應(yīng)用的技術(shù)棧,微應(yīng)用具備完全自主權(quán)
獨(dú)立開發(fā)、獨(dú)立部署
微應(yīng)用倉庫獨(dú)立,前后端可獨(dú)立開發(fā),部署完成后主框架自動完成同步更新
增量升級
在面對各種復(fù)雜場景時(shí),我們通常很難對一個(gè)已經(jīng)存在的系統(tǒng)做全量的技術(shù)棧升級或重構(gòu),而微前端是一種非常好的實(shí)施漸進(jìn)式重構(gòu)的手段和策略
獨(dú)立運(yùn)行時(shí)
每個(gè)微應(yīng)用之間狀態(tài)隔離,運(yùn)行時(shí)狀態(tài)不共享
摘自 qiankun官方文檔
主應(yīng)用配置
此次項(xiàng)目 主應(yīng)用與 子應(yīng)用均為 vue ,
下載 qiankun
npm install qiankun
在主應(yīng)用中注冊微應(yīng)用
// 導(dǎo)入乾坤函數(shù)
import {
registerMicroApps,
setDefaultMountApp,
start
} from "qiankun";
封裝 render 方法
此方法在main.js 中要初始調(diào)用一次, 主要用來掛載主應(yīng)用 , 之后子應(yīng)用分別依次調(diào)用 ,所以故作判斷. 傳入的參數(shù)分別為 子應(yīng)用 的 HTML 和 加載狀態(tài) content 字段 我們用 vuex 存儲 起來,方便使用
let app = null;
function render({ appContent, loading }) {
if (!app) {
app = new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
} else {
store.commit('microApp/changeCenter', appContent);
store.commit('microApp/changeLoading', loading);
}
}
微應(yīng)用注冊
下文中的apps 可以為獲取后數(shù)據(jù) , 注冊微應(yīng)用 本文案例比較簡單,方便大家理解 ,
在注冊自應(yīng)用的參數(shù) ** container 與 render** 采坑比較多,下邊會著重講解.
function genActiveRule(routerPrefix) {
return location => location.pathname.startsWith(routerPrefix);
}
//傳遞給子應(yīng)用的數(shù)據(jù)
let msg = {

data:'修煉愛情的辛酸,學(xué)會放好以前的渴望'
}
let apps = [
{
name: 'linjunjie',
entry: '//localhost:215', // 改成自己子應(yīng)用的端口號
container:'#subView', //節(jié)點(diǎn) id // 沙盒模式
// render:render, // 普通模式
activeRule: genActiveRule('/star'),
props:msg
}
]
//注冊的子應(yīng)用 參數(shù)為數(shù)組
registerMicroApps(apps,{
beforeLoad: [
app => {
console.log(app)
console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
},
],
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
},
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
},
],
});
setDefaultMountApp('/star/linjunjie')
//開啟沙盒模式
start({
sandbox :{strictStyleIsolation: true}
})
當(dāng)微應(yīng)用信息注冊完之后,一旦瀏覽器的 url 發(fā)生變化,便會自動觸發(fā) qiankun 的匹配邏輯,所有 activeRule 規(guī)則匹配上的微應(yīng)用就會被插入到指定的 container 中,同時(shí)依次調(diào)用微應(yīng)用暴露出的生命周期鉤子。
主應(yīng)用為子應(yīng)用準(zhǔn)備的 展示元素
<template>
<div id="app">
<div id="nav">
<!--//主應(yīng)用 為子應(yīng)用的跳轉(zhuǎn)dom-->
<div @click="onChangePage('/star/linjunjie')" >林俊杰</div>
<div @click="onChangePage('/star/zhangyixin')" >張藝興</div>
</div>
<!--//用來展子應(yīng)用的 內(nèi)容區(qū)-->
<div id="subView" class="sub-content-wrap" v-html="content"></div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default{
data(){
return {
}
},
computed:{
//獲取子應(yīng)用HTML 數(shù)據(jù)
...mapState('microApp', ['content']),
...mapState('microApp', ['mircoAppLoading']),
},
methods:{
//定義跳轉(zhuǎn)方法
onChangePage(url){
console.log(url)
this.routerGo(url, '我喜愛的男明星')
},
routerGo(href = '/', title = null, stateObj = {}) {
window.history.pushState(stateObj, title, href);
},
}
}
</script>
子應(yīng)用配置
關(guān)于子應(yīng)用的配置相對較簡單 , 不需要額外下載qiankun 主要將生命鉤子 導(dǎo)出即可
導(dǎo)出響應(yīng)的生命鉤子
導(dǎo)出 bootstrap、mount、unmount 三個(gè)生命周期鉤子,以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用。注意,實(shí)例化路由時(shí),判斷當(dāng)運(yùn)行在qiankun環(huán)境時(shí),路由要添加前綴,前綴與主應(yīng)用注冊子應(yīng)用函數(shù)genActiveRule("/subdemo")內(nèi)的參數(shù)一致
'star' 值需要與主應(yīng)用的值對應(yīng) genActiveRule("/star") 中的值需要商定好 主應(yīng)用與微應(yīng)用都要使用
如果 new VueRouter 不在main.js 中 配置 ,請將此配置移動到 main.js 方便管理
import routes from './router' //將路由信息導(dǎo)出方便使用
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/star' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
//props 包含主應(yīng)用傳遞的參數(shù) 也包括為子應(yīng)用 創(chuàng)建的節(jié)點(diǎn)信息
console.log(props)
render(props);
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
配置微應(yīng)用的打包工具
除了代碼中暴露出相應(yīng)的生命周期鉤子之外,為了讓主應(yīng)用能正確識別微應(yīng)用暴露出來的一些信息,微應(yīng)用的打包工具需要在vue.config.js 中 增加如下配置:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
子應(yīng)用判斷
子應(yīng)用中新建 publicPath.js 在main.js 引入
if (window.__POWERED_BY_QIANKUN__) {
//處理資源
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
處理 資源加載問題
配置 vue.config.js
module.exports = {
publicPath:`//localhost:${port}`,
}
vue.config.js 完整配置
const path = require('path');
const packageName = require('./package').name;
function resolve(dir) {
return path.join(__dirname, dir);
}
const port = 7101; // dev port
module.exports = {
publicPath:`//localhost:${port}`,
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
devServer: {
// host: '0.0.0.0',
hot: true,
historyApiFallback: true,//添加 重點(diǎn)
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
},
};
采坑記錄
當(dāng)前頁面為子應(yīng)用時(shí), 刷新頁面404
以下方式均為主應(yīng)用配置
方式一 刪除 mode 配置項(xiàng)
mode: 'history', // 將此配置代碼刪除方式二 配置404 頁面
如果沒有注釋掉mode: 'history' 此參數(shù) 將404 頁面重新導(dǎo)向 home首頁
{
path: '*',
name: 'indexNotFound',
component: resolve => require(['@/components/home'], resolve),
children: HomeChild,
},
子應(yīng)用 樣式隔離 開始沙箱模式 遇到的問題
主應(yīng)用配置
sandbox :{strictStyleIsolation: true}渲染模式由 render 模式 改為 containercontainer:'#subView', 此時(shí) 子應(yīng)用的 掛載 dom 為<div id="subView"> </div>謹(jǐn)記主 container :#+id子應(yīng)用配置 上文有提到 主要代碼 截取
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app'); //重點(diǎn)
遇到的問題: 開啟沙箱模式,如果是 采用 render 模式會報(bào)錯(cuò) ,固選擇container 模式
效果圖
寫到這里,項(xiàng)目已經(jīng)構(gòu)建完成了 讓我們來看看效果吧
這里是完整代碼 方便大家學(xué)習(xí) 代碼github地址:https://github.com/zxh1307/qiankun-vue
項(xiàng)目問題
為啥我項(xiàng)目啟動后看不到子應(yīng)用的效果
將master 主應(yīng)用 main.js 中 注冊的 子應(yīng)用的端口號 改成自己項(xiàng)目的端口號即可
結(jié)語
開發(fā)中還有其他坑 忘記記錄了, 千萬記得項(xiàng)目部署子應(yīng)用資源跨域的問題 , 需要Nginx配置跨域問題.
