微前端改造
這里以Vue3為例子,主應用和子應用均使用vue3
路由的話,建議主應用和子應用使用相同模式,即均為history或者均為hash
以下先使用為history模式講解纺座,最后會寫如何使用hash模式。
History模式
主應用改造
一般情況下溉潭,我們會將帶導航的layout
的部分净响,直接放在主應用中少欺。當然不是說不能拆,是能拆的馋贤,因為導航的layout
明顯是個路由不敏感部分赞别,完全可以拆解為單獨的子應用。
1.添加qiankun
yarn add qiankun
2.vue.config.js
其實沒什么要改的配乓,但我這還是建議把這兩個加上
module.exports = {
publicPath: '/main/',
// 修改打包名
outputDir: 'main',
};
3.router.js
const router = createRouter({
// 這里就是publicPath了
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
4.加載微應用的改造仿滔,可以在src下建一個micro目錄
|----index.js //注冊
|----store.js //應用間通信
|----subapps.js //配置信息
4.1 index.js
import { registerMicroApps } from 'qiankun';
import subapps from './subapps';
// 判斷是否以某字符串開頭
// 注冊并加載
function register() {
// 注冊微應用
registerMicroApps(subapps, {
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: [
(app) => console.log('before mount', app.name),
],
afterMount: [
(app) => console.log('before mount', app.name),
],
beforeUnmount: [
(app) => console.log('before mount', app.name),
],
afterUnmount: [
(app) => console.log('before mount', app.name),
],
});
}
export default register;
4.2 store.js
應用間通信,qiankun提供了一個簡單的apiinitGlobalState
但是這玩意不是“響應式”的犹芹,換句話說崎页,它改變不會引起頁面變化。
得益于vue3提供的reactive
腰埂,我們可以很方便的構(gòu)造一個響應式飒焦。
(同時也因為非常方便,所以也有很多大佬喊出了不再需要vuex屿笼,建議搜搜看牺荠,很有意思,當然這是題外話驴一。)
并將這個響應式用于頁面展示志电。
// 應用間通信
import { initGlobalState } from 'qiankun';
import { reactive } from 'vue';
// 初始化state,加reative使其變?yōu)轫憫?const initialState = reactive({
token: '123',
});
const actions = initGlobalState(initialState);
// 監(jiān)聽全局變化
actions.onGlobalStateChange((newState, oldState) => {
console.log('主應用監(jiān)聽', '變化前', oldState, '變化后', newState);
Object.keys(newState).forEach((key) => {
initialState[key] = newState[key];
});
}, true);
// 獲取globalState
function getGlobalState(key) {
return key ? initialState[key] : initialState;
}
// 更新通知所有微應用
function setGlobalState(globalState) {
actions.setGlobalState(globalState);
}
// 卸載全局變化
function offGlobalStateChange() {
actions.offGlobalStateChange();
}
export default {
actions,
getGlobalState,
setGlobalState,
offGlobalStateChange,
};
4.2 subapps.js
import router from '@/router/index';
// 此時在開發(fā),測試環(huán)境蛔趴,理論上已經(jīng)掛載了subapps的入口
const baseUrl = process.env.BASE_URL;
const subapps = [
{
name: 'sub-login',
entry: process.env.VUE_APP_SUB_LOGIN,
container: '#sub-apps',
activeRule: `${baseUrl}sub-login`,
props: {
routeBasePath: '/sub-login',
mainRouter: router,
},
},
{
name: 'sub-user-manage',
entry: process.env.VUE_APP_SUB_USER_MANAGE,
container: '#sub-apps',
activeRule: `${baseUrl}sub-user-manage`,
props: {
routeBasePath: '/sub-user-manage',
mainRouter: router,
},
},
];
export default subapps;
name,entry例朱,container孝情,activeRule
在第上一篇已經(jīng)介紹過了
這里多了一個props,props意思是給子應用獲取的對象洒嗤,意味著有些東西箫荡,可以從主應用往下傳遞給子應用。這里我傳遞了兩個值
routeBasePath:子應用的路由地址前綴渔隶,在history模式下用于填寫子應用的basepath羔挡,非常有用
mainRouter:主應用的router,在history模式下應用間跳轉(zhuǎn)非常有用
這個東西我們還可以做點有意思的操作间唉,比如:無星的微前端之旅(四)——qiankun線上服務代理到本地
5.提供掛載節(jié)點#sub-apps
我們在路由定義中绞灼,把所有子應用的components都匹配到一個View中。
這個沒什么寫代碼的意義呈野,截個圖直接掠過低矮。
這個view提供一個dom節(jié)點用于后續(xù)掛載和啟動
<template>
<div id="sub-apps" />
</template>
<script>
import Actions from '@/micro/store';
import {
defineComponent, onMounted, onUnmounted,
} from 'vue';
import { start } from 'qiankun';
export default defineComponent({
name: 'SubApps',
setup() {
onMounted(async () => {
console.log('Subapps頁面加載');
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
});
onUnmounted(() => {
if (window.qiankunStarted) {
window.qiankunStarted = false;
Actions.offGlobalStateChange();
}
});
return {
};
},
});
</script>
<style lang="less">
#sub-apps {
height: 100%;
>div:first-child {
height: 100%;
}
}
</style>
這里有一個需要注意的點。不是js代碼被冒,而是css军掂。
主應用加載子應用的時候轮蜕,會新增一個qiankun的div盒子,可能這個盒子會影響樣式蝗锥,導致?lián)尾婚_跃洛。所以需要使用css選擇器讓qiankun注入的盒子加一些css進行改變以達到預期效果。
6.main.js修改
改造前:
// 默認生成的main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
改造后:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// 注冊
import register from './micro/index';
// 添加全局通信
import '@/micro/store';
createApp(App).use(store).use(router).mount('#app');
// 注冊
register();
到此為止终议,主應用改造完畢汇竭。
子應用改造
1.vue.config.js添加核心配置
const packageName = require('./package.json').name;
module.exports = {
// 先寫為/,后續(xù)會修改
publicPath: '/',
// 輸出目錄重命名為項目名稱痊剖,方便后期部署
outputDir: packageName,
// 來自qiankun文檔的修改
configureWebpack: {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
},
devServer: {
open: true,
// 建議添加端口韩玩,不同模塊加載不同端口,方便開發(fā)制定加載
port: 3001,
// 必須添加陆馁,qiankun需要支持跨域
headers: {
'Access-Control-Allow-Origin': '*',
},
},
};
2.添加public-path
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
它的作用找颓,點擊標題查看文檔
3.src文件夾下新建一個micro/store.js,用于應用間通信相關(guān)
// 全局數(shù)據(jù)store
import { reactive } from 'vue';
let actions = null;
const initialState = reactive({});
let routeBasePath = '';
function setActions(tmpActions) {
// 如果有興趣叮贩,還可以把這里的初始化和vuex關(guān)聯(lián)起來
actions = tmpActions;
actions.onGlobalStateChange((newState, oldState) => {
console.log('子應用監(jiān)聽', '變化前', oldState, '變化后', newState);
Object.keys(newState).forEach((key) => {
initialState[key] = newState[key];
});
}, true);
}
function setGlobalState(state) {
return actions.setGlobalState(state);
}
function getActions() {
return actions;
}
function getGlobalState(key) {
return key ? initialState[key] : initialState;
}
// 基礎(chǔ)數(shù)據(jù)
function setRouteBasePath(path) {
routeBasePath = path;
}
// 基礎(chǔ)數(shù)據(jù)
function getRouteBasePath() {
return routeBasePath;
}
export default {
setActions,
getActions,
setGlobalState,
getGlobalState,
setRouteBasePath,
getRouteBasePath,
};
4.router改造
import { createRouter, createWebHistory } from 'vue-router';
import Actions from '@/micro/store';
const routes = [
{
path: '/',
name: 'Login',
component: () => import('../views/Login/index.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register/index.vue'),
},
{
path: '/config',
name: 'Config',
component: () => import('../views/Config/index.vue'),
},
];
const setupRouter = () => createRouter({
// 獲取來自主應用的前綴
history: createWebHistory(Actions.getRouteBasePath()),
routes,
});
export default setupRouter;
5.main.js改造
改造前:
// 默認生成的main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
改造后:
// 注意哦击狮,這一行引入不要忘記了,要加載最上面
import './public-path';
import { createApp } from 'vue';
import Actions from '@/micro/store';
import App from './App.vue';
import store from './store';
import setupRouter from './router';
let instance = null;
let router = null;
function render(props = {}) {
const { container, routeBasePath, mainRouter } = props;
// 這里需要注意益老,往下注入的必須和子打包配置一致
Actions.setRouteBasePath(routeBasePath || process.env.BASE_URL);
router = setupRouter();
instance = createApp(App);
instance.use(router);
instance.use(store);
router.isReady().then(() => {
instance.mount(container ? container.querySelector('#app') : '#app');
});
}
if (!window.__POWERED_BY_QIANKUN__) {
// 如果是非乾坤訪問彪蓬,意思是子應用單獨訪問
render();
}
export async function bootstrap() {
console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}
export async function mount(props) {
// 獲取props中的全局通信
Actions.setActions(props);
// 只有從qiankun訪問,才會到這個生命周期
render(props);
}
export async function unmount() {
// 這里千萬不要忘記置空^嗝取5刀!桃纯!
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
}
好了酷誓,子應用到此就改造完畢了。
Hash
如果是hash模式态坦,那么需要變動的地方就比較多了
1.主應用和子應用中vue.config.js全部修改為
module.exports = {
publicPath: './',
}
2.主應用和子應用中router修改為
const setupRouter = () => createRouter({
history: createWebHashHistory(),
routes,
});
3.主應用subapps.js修改為
import router from '@/router/index';
// 此時在開發(fā)盐数,測試環(huán)境,理論上已經(jīng)掛載了subapps的入口
const baseUrl = '#/';
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
const subapps = [
{
name: 'sub-login',
entry: process.env.VUE_APP_SUB_LOGIN,
container: '#sub-apps',
activeRule: getActiveRule(`${baseUrl}sub-login`),
props: {
routeBasePath: '/sub-login',
mainRouter: router,
},
},
{
name: 'sub-user-manage',
entry: process.env.VUE_APP_SUB_USER_MANAGE,
container: '#sub-apps',
activeRule: getActiveRule(`${baseUrl}sub-login`),
props: {
routeBasePath: '/sub-user-manage',
mainRouter: router,
},
},
];
export default subapps;
修改完畢伞梯。
至此玫氢,主應用和子應用均修改完畢。