无星的微前端之旅(三)——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,我们可以很方便的构造一个响应式。
(同时也因为非常方便,所以也有很多大佬喊出了不再需要vuex,建议搜搜看,很有意思,当然这是题外话。)

并将这个响应式用于页面展示。

// 应用间通信
import { initGlobalState } from 'qiankun';
import { reactive } from 'vue';

// 初始化state,加reative使其变为响应式
const initialState = reactive({
  token: '123',
});

const actions = initGlobalState(initialState);

// 监听全局变化
actions.onGlobalStateChange((newState, oldState) => {
  console.log('主应用监听', '变化前', 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';
// 此时在开发,测试环境,理论上已经挂载了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

在第上一篇已经介绍过了

这里多了一个props,props意思是给子应用获取的对象,意味着有些东西,可以从主应用往下传递给子应用。这里我传递了两个值

routeBasePath:子应用的路由地址前缀,在history模式下用于填写子应用的basepath,非常有用

mainRouter:主应用的router,在history模式下应用间跳转非常有用

这个东西我们还可以做点有意思的操作,比如:无星的微前端之旅(四)——qiankun线上服务代理到本地

5.提供挂载节点#sub-apps

我们在路由定义中,把所有子应用的components都匹配到一个View中。

这个没什么写代码的意义,截个图直接掠过。

1.png

这个view提供一个dom节点用于后续挂载和启动





如何在主应用的某个路由页面加载微应用

这里有一个需要注意的点。不是js代码,而是css。

主应用加载子应用的时候,会新增一个qiankun的div盒子,可能这个盒子会影响样式,导致撑不开。所以需要使用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 = {
// 先写为/,后续会修改
  publicPath: '/',
//   输出目录重命名为项目名称,方便后期部署
  outputDir: packageName,
// 来自qiankun文档的修改
  configureWebpack: {
    output: {
      library: `${packageName}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${packageName}`,
    },
  },
  devServer: {
    open: true,
    // 建议添加端口,不同模块加载不同端口,方便开发制定加载
    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,用于应用间通信相关

// 全局数据store
import { reactive } from 'vue';

let actions = null;
const initialState = reactive({});
let routeBasePath = '';
function setActions(tmpActions) {
  // 如果有兴趣,还可以把这里的初始化和vuex关联起来
  actions = tmpActions;
  actions.onGlobalStateChange((newState, oldState) => {
    console.log('子应用监听', '变化前', 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;
}
// 基础数据
function setRouteBasePath(path) {
  routeBasePath = path;
}
// 基础数据
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() {
  // 这里千万不要忘记置空!!!!
  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';
// 此时在开发,测试环境,理论上已经挂载了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;

修改完毕。

至此,主应用和子应用均修改完毕。

你可能感兴趣的:(无星的微前端之旅(三)——qiankun改造)