無星的微前端之旅(三)——qiankun改造

微前端改造

這里以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中。

這個沒什么寫代碼的意義呈野,截個圖直接掠過低矮。

1.png

這個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進行改變以達到預期效果。

image.png

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;

修改完畢伞梯。

至此玫氢,主應用和子應用均修改完畢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谜诫,一起剝皮案震驚了整個濱河市漾峡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猜绣,老刑警劉巖灰殴,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡牺陶,警方通過查閱死者的電腦和手機伟阔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掰伸,“玉大人皱炉,你說我怎么就攤上這事∈ㄑ迹” “怎么了合搅?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長歧蕉。 經(jīng)常有香客問我灾部,道長,這世上最難降的妖魔是什么惯退? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任赌髓,我火速辦了婚禮,結(jié)果婚禮上催跪,老公的妹妹穿的比我還像新娘锁蠕。我一直安慰自己,他們只是感情好懊蒸,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布荣倾。 她就那樣靜靜地躺著,像睡著了一般骑丸。 火紅的嫁衣襯著肌膚如雪舌仍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天通危,我揣著相機與錄音抡笼,去河邊找鬼。 笑死黄鳍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的平匈。 我是一名探鬼主播框沟,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼增炭!你這毒婦竟也來了忍燥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤隙姿,失蹤者是張志新(化名)和其女友劉穎梅垄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體输玷,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡队丝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年靡馁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片机久。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡臭墨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膘盖,到底是詐尸還是另有隱情胧弛,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布侠畔,位于F島的核電站结缚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏软棺。R本人自食惡果不足惜红竭,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望码党。 院中可真熱鬧德崭,春花似錦、人聲如沸揖盘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兽狭。三九已至憾股,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箕慧,已是汗流浹背服球。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颠焦,地道東北人斩熊。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像伐庭,于是被迫代替她去往敵國和親粉渠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容