爲了業務在原有系統中,集成新項目,咱們常常會使用qiankun這套微前端解決方案,來解決集成訴求。javascript
vue2,vue3在qiankun中的使用實際差異不大,主要路由處理上有些區別,也會在文中指出,會以【vue3】做爲標識。html
經過這邊文章,能夠了解到,如何在vue(2/3)技術棧裏使用微前端集成新老項目,主要內容包括:前端
下面是經常使用的兩個qiankun API,已給出連接,能夠在官網查看具體用法:vue
registerMicroApps: 主應用註冊微前端應用java
start: 啓動微前端應用webpack
import { registerMicroApps, start } from 'qiankun';
registerMicroApps(
[
{
// 微前端應用名
name: 'app1',
// 微前端啓動地址
entry: '//localhost:8100/',
// 微前端掛載dom
container: '#app',
// 微前端觸發路由
activeRule: '#/app1',
// 主應用向子應用傳遞的靜態值
props: {
name: 'yuxiaoyu',
},
},
],
);
start();
複製代碼
// 入口文件main.js
// 子應用並不用引入qiankun,只要暴露響應的聲明週期鉤子給主應用使用就ok
// 掛載實例
function render(props: any = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
app.use(store);
router.isReady().then(() => {
app.mount(container ? container.querySelector('#container') : '#container');
});
}
// 微應用在主應用運行時,主應用會在微應用中掛載window.__POWERED_BY_QIANKUN__,能夠用於判斷環境
// 官方提供了下面這個webpack注入publicPath的方法, 開發環境咱們這麼使用,生產改到vue.config.js中,後面再介紹。
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// 若是是獨立運行 window.__POWERED_BY_QIANKUN__=undefined 直接render
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 最後暴露的三個方法是固定的,加載渲染以及銷燬
export async function bootstrap() { }
export async function mount(props: any) {
render(props);
}
export async function unmount() {
app.unmount();
app._container.innerHTML = "";
app = null;
// 這裏reload的緣由: 由於在項目中,微應用和主應用沒有共用導航等信息
// 至關於兩個獨立的頁面,因此就共用了<div id="app">
// 在卸載微應用後,爲了再把主應用渲染出來,就從新reload了一遍。
location.reload();
}
複製代碼
// router改造
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// 由於主應用在激活子應用時,有一個activeRule前綴,因此在hash模式下,咱們須要給每一個一級路由都添加activeRule的前綴,主應用爲'#/app1',那麼子應用前咱們就加'/app1'就能夠了。
export default [
{
path: '/app1/fujidaohang',
redirect: '/app1/fujidaohang/zijidaohang',
component: BothLayout,
name: 'fujidaohang',
meta: {
title: '父級導航',
navPosition: 'top',
},
children: [
{
path: 'zijidaohang',
component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
name: 'zijidaohang',
meta: {
title: '子級導航',
navPosition: '',
},
},
],
},
{
path: '/app1/course',
component: Course,
name: 'course',
children: [],
}
];
複製代碼
// vue.config.js改造
const packageName = require('./package.json').name;
module.exports = {
...
// 用於主應用識別子應用,固定寫法
configureWebpack: {
output: {
library: 'app1',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
}
}
複製代碼
構建部署能夠選擇兩種方式:web
同域名、不一樣域名部署。vue-router
1.能夠考慮將子應用打包放在static路徑下,做爲主應用的靜態資源引入。這種處理方式的適用場景包含:子應用不須要獨立訪問、子應用不須要獨立部署。這種方式的好處在於,不用運維配合去Nginx配置,本地打包,並使用原有的部署方式。固然,它同時也失去了微前端,主、子獨立部署的優勢。vue-cli
2.還有一種方式,能夠將打包後的靜態資源在同一臺服務器進行部署,而後針對於項目新增部署和Nginx相關配置。在主應用訪問的時候能夠經過域名加載,也能夠經過相對路徑進行加載(由於咱們同臺機器部署)。這個相對於上面的方案,增長了運維成本,但保留了主子獨立發佈的特性。json
這裏的方案和上面的2
差很少,區別在於資源可能放在不一樣的服務器上,因此只能經過域名進行資源的加載(也就是registerMicroApps中配置的entry須要是域名,不能是相對路徑了,和開發環境差很少),記得處理跨域。
固然也能夠根據須要選用相應的部署方式,官方都有給出詳細的介紹: 如何部署。
// main.js
// 註冊微應用
registerMicroApps(
[
{
name: 'app1',
// 路徑改成部署後,微應用要存放在主應用的目錄,其他不變
entry: '/static/index.html',
container: '#app',
activeRule: '#/app1',
props: {
name: 'kuitos',
},
},
],
);
複製代碼
// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }
// vue.config.js
module.exports = {
...
// 這裏新增打包資源存放路徑,與上面主應用相對應
publicPath: '/static'
}
複製代碼
這裏咱們和hash-dev
模式進行對比,只列舉差別的部分。
// 主應用
// main.js, 註冊微應用有變化
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:8101',
container: '#app',
// 改變點:激活路由由hash模式變爲history模式
activeRule: '/app1',
props: {
name: 'yuxiaoyu',
},
},
],
);
// router.js
const router = new VueRouter({
// 由hash改成history模式
mode: 'history',
base: process.env.BASE_URL,
routes,
});
複製代碼
// 子應用
// router/index.js
// 【vue3】由於子應用爲vue3版本,這裏是vue2和vue3自己路由處理上的區別,咱們只是使用了vue3的寫法加上了須要的前綴app1
const router = createRouter({
// 改成history模式,而且加activeRule前綴
history: createWebHistory('/app1'),
routes,
});
// router.js
// 去掉這裏的app1前綴
export default [
{
path: '/fujidaohang',
redirect: '/fujidaohang/zijidaohang',
component: BothLayout,
name: 'fujidaohang',
meta: {
title: '父級導航',
navPosition: 'top',
},
children: [
{
path: 'zijidaohang',
component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
name: 'zijidaohang',
meta: {
title: '子級導航',
navPosition: '',
},
},
],
},
{
path: '/course',
component: Course,
name: 'course',
children: [],
}
];
複製代碼
這裏與hash模式改動點是一致的,只描述不一樣點。
// main.js
// 註冊微應用
registerMicroApps(
[
{
// 路徑改成部署後,微應用要存放在主應用的目錄,其他不變
entry: '/static/index.html',
},
],
);
複製代碼
// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }
// vue.config.js
module.exports = {
...
// 這裏新增打包資源存放路徑,與上面主應用相對應
publicPath: '/static'
}
複製代碼
當進入微應用時,咱們鑑定微應用是否登陸時,咱們能夠考慮在微應用作鑑權,也能夠在主應用作鑑權。
這裏介紹一種在主應用內鑑權的方案。
qiankun裏在註冊微應用時,registerMicroApps
提供了第二個參數,lifeCycles
- 可選,全局的微應用生命週期鉤子
registerMicroApps(
[{...}], {
// 這裏採用在進入微前端以前進行鑑權,確保進入微前端時,已經登陸。
beforeLoad: [
() => {
if (!auth.check()) {
vm.$router.push(loginPath);
}
},
],
},);
複製代碼
這裏官方提供了修改antd前綴的方法,如將ant
改成dida-ant
, 如何確保主應用跟微應用之間的樣式隔離。
這裏ant-design-vue文檔裏雖然沒有提供prefixCls
的參數,可是可使用的,源碼裏相應的處理。
這裏咱們能夠採用官方提供的start(options?)
,將微應用放入瀏覽器所支持的shadow dom
中。
start({
sandbox: {
// 主應用 & 子應用樣式隔離
strictStyleIsolation: true, // 放入shadow dom中
}
});
複製代碼
這裏咱們能夠看到微前端被放入了shadow-root
裏,對於shadow dom能夠經過這裏進行了解。
這樣隔離後,在咱們使用ant design
這種外部庫時會有一些問題,例如popup組件
,本來實現是掛在document.body
中的,咱們將子應用放到了shadow dom
中,那就須要將popup
也掛進去。ant design
官方提供了方法,搜索getPopupContainer
;
咱們能夠經過註冊微應用時,經過props參數進行數據的傳遞,在mounted
和render
生命週期中,能夠拿到props
數據。
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:8100',
container: '#app',
activeRule: '/app1',
props: {
id: 1,
},
},
],
);
複製代碼
qiankun官方提供了建立主、子應用通訊的定義方法。initGlobalState(state)
,文檔連接,初始化後會返回三個方法,onGlobalStateChange
,setGlobalState
,offGlobalStateChange
,分別是監聽,設置和移除。
// 主應用
// 初始化 state
// 調用initGlobalState後,會掛在props中進行傳遞
const initialState = {
userInfo: {}, // 用戶信息
};
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((newState, oldState) => {
// newState: 變動後的狀態; oldState 變動前的狀態
console.log('mainapp: global state changed', newState, oldState);
});
actions.setGlobalState({
userInfo: {
name: 'Zhangsan',
},
});
actions.offGlobalStateChange();
複製代碼
export async function mount(props: any) {
render(props);
// props 會注入onGlobalStateChange、setGlobalState方法。
console.log('props :>> ', props);
app.config.globalProperties.$onGlobalStateChange =props.onGlobalStateChange;
app.config.globalProperties.$setGlobalState = props.setGlobalState;
props.onGlobalStateChange((newState: any, oldState: any) => {
// newState: 變動後的狀態; oldState 變動前的狀態
console.log('microapp: global state changed', newState, oldState);
});
window.document.addEventListener('click', () => {
props.setGlobalState({
userInfo: {
name: Math.random(),
},
});
});
}
複製代碼
vue3做爲主應用, 原系統做爲子應用(vue2)。也就是vue3(主)接vue2(子)。在已有路由或組件中引入子應用。 如圖:
官方提供了一種在應用某個路由下面接入方法。
官方提供的方法是在vue2爲主應用時接入。因此這裏寫法上有會一些區別。
{
path: '/huodongguanli',
redirect: '/trace_micro/project/projectList',
component: SideLayout,
name: 'huodongguanli',
meta: {
title: '活動管理',
navPosition: 'side',
icon: () => {
return h(ContactsOutlined);
},
},
children: [
{
path: '/trace_micro/project/projectList', // 須要嵌套在二級菜單,且路徑不須要一級的path拼接的,這裏寫絕對路徑(這裏爲業務邏輯,不用關注)
component: { default: '' }, // 由於這裏只是爲了聲明一個menu項,並不須要組件,(組件內容在子應用中),這裏給個含有default的空對象
name: 'lianluliebiao',
meta: {
title: '鏈路列表',
navPosition: 'side',
},
},
{
path: 'chuangjianjiangzuo', // 主應用中的路由 正常寫就能夠
name: 'chuangjianjiangzuo',
meta: {
title: '建立講座',
navPosition: 'side',
},
},
],
},
// 針對於不用在主應用裏菜單顯示出來的路由,都進行模糊匹配
// 【vue3】
// 由於vue3中,vue-router4.x[取消了通配符的寫法](https://next.router.vuejs.org/zh/guide/migration/index.html#%E5%88%A0%E9%99%A4%E4%BA%86-%EF%BC%88%E6%98%9F%E6%A0%87%E6%88%96%E9%80%9A%E9%85%8D%E7%AC%A6%EF%BC%89%E8%B7%AF%E7%94%B1),因此在匹配的`path`的寫法上會有一些區別:
{
path: '/trace_micro/:pathMatch(.*)*', // 【重要】這裏咱們採用router4.x中提供的寫法去匹配子應用的路由
hidden: true,
component: SideLayout,
name: 'projectOperate',
meta: {
title: '建立鏈路',
navPosition: 'side',
},
}
複製代碼
其他的使用方法(start
和掛載節點
等)與官方提供的一致。
官方有提供常見問題文檔,遇到問題時,能夠先去這裏查看一下是否有相應的匹配解決方案。
這裏也列舉幾個,我在接入項目時候遇到的其它的坑:
#app
。在返回主應用時,主應用不顯示。這裏由於咱們在初始化微前端時,把#app
中的內容替換成了子應用的數據,在返回時,並無從新掛載,目前能夠經過reload
簡單處理。
#app
。dom看起來掛載成功了,可是樣式都沒有。這個和第一個又是不一樣的狀況,主要是由於咱們把子應用的dom結構直接掛載到了主應用上,微前端這一套都沒有生效。在mount
生命週期中,咱們能夠拿到props,這裏面掛載了一個container
屬性,就是掛載微前端的dom節點,能夠經過props.container.querySelector('#app')
去掛載子應用的vue
。
$router.push
不生效是由於咱們主、子應用使用的router
並非一個實例,註冊的路由也不同,因此子應用並不能匹配到主應用的路由,可使用history.push
或者location.href
進行跳轉。
js
加載不到,或者<
等語法不對的問題通常因爲主應用引入子應用的路徑不對,檢查路徑是否能引到子應用資源。
babel-polyfill
時,有時候會babel-polyfill
引入兩次的問題。這個先檢查一下是否子應用也單獨引入babel-polyfill
了,若是引入了就都統一放到一處去引用。
如今大部分新的應用都是用vue-cli進行生成,並不會單獨引入babel-polyfill
,那若是報這個錯了,就檢查一下是否是,子應用的路徑有問題,引入的資源不對。報了引兩次的問題,可能也是因爲子應用沒有引到的問題。和babel-polyfill
自己引入沒有關係。
上面也有給出樣式隔離的方案,但方案自己仍是有一些問題的。好比咱們提到的popup
掛在document.body
上的問題,須要處理。可能還會有一些其它的問題要處理。
最簡單有效的方法仍是在主、子應用中,儘可能使用style scoped
進行組件級別的樣式隔離。對於使用同一組件庫,不一樣配置,能夠經過修改組件、樣式前綴解決。
以上就是微前端(qiankun)在vue二、3中的實戰開發,但願對各位有所幫助。