前端微服务-阿里乾坤

679 阅读2分钟

image.png

阿里乾坤官网

乾坤

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级

    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

安装

yarn add qiankun || npm i qiankun -S

主应用注册

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'react app', // app name registered
    entry: '//localhost:7100',
    container: '#yourContainer',
    activeRule: '/yourActiveRule',
  },
  {
    name: 'vue app',
    entry: { scripts: ['//localhost:7100/main.js'] },
    container: '#yourContainer2',
    activeRule: '/yourActiveRule2',
  },
]);

start();

App.vue

<template>
  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/c1/">c1--Home</router-link> |
    <router-link to="/c1/about">c1--About</router-link>
  </nav>
  <router-view/>
  <div id="c1Container"></div>
</template>

注册子应用

src/public-path.js

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

main.js

import './public-path';
import { createApp } from 'vue'
import App from './App.vue'
import routes from './router'
import { createRouter, createWebHistory } from 'vue-router';
import store from './store'
let router = null;
let instance = null;
let history = null;
 
function render(props = {}) {
    const { container } = props;
    let arr = routes;
    console.log(arr)
    history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/c1' : '/');
    router = createRouter({
        history,
        routes: routes,
    });
    instance = createApp(App);
    instance.use(router);
    instance.use(store);
    instance.mount(container ? container.querySelector('#app') : '#app');
}
 
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}
 
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
    console.log('%c%s', 'color: green;', '微应用初始化vue3.0 app bootstraped');
}
 
function storeTest(props) {
    props.onGlobalStateChange &&
    props.onGlobalStateChange(
        (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
        true,
    );
    props.setGlobalState &&
    props.setGlobalState({
        ignore: props.name,
        user: {
            name: props.name,
        },
    });
}
 
export async function mount(props) {
    storeTest(props);
    render(props);
    instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
    instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}
 
export async function unmount() {
    instance.unmount();
    instance._container.innerHTML = '';
    instance = null;
    router = null;
    history.destroy();
}

vue.config.js

const { name } = require('./package');
module.exports = {
  devServer: {
    port: 7100, // 启动项目时的端口号
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
};

至此 分别run 父子应用即可看到运行正常 符合预期

业务场景中 通信是不可避免的

父子应用之间相互通信

initGlobalState(state)

  • 参数

    • state - Record<string, any> - 必选
  • 用法

    定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。

  • 返回

    • MicroAppStateActions

      • onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
      • setGlobalState: (state: Record<string, any>) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
      • offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用

示例

主应用创建 actions.js

import { initGlobalState } from 'qiankun'
const initState = {
    username:"古力娜扎"
}
const actions = initGlobalState(initState)
actions.onGlobalStateChange((state, preState) => {
    console.log(preState, '主应用变更前');
    console.log(state, '主应用变更后');

})
export default actions;

main.ts 注册引入 通过props传入子应用

image.png

子应用新建actions.js

function emptyAction() {
    // 提示当前使用的是空 Action
    console.warn("Current execute action is empty!");
}

class Actions {
    // 默认值为空 Action
    actions = {
        onGlobalStateChange: emptyAction,
        setGlobalState: emptyAction,
    };

    /**
     * 设置 actions
     */
    setActions(actions) {
        this.actions = actions;
    }

    /**
     * 映射
     */
    onGlobalStateChange() {
        return this.actions.onGlobalStateChange(...arguments);
    }

    /**
     * 映射
     */
    setGlobalState() {
        return this.actions.setGlobalState(...arguments);
    }
}

const actions = new Actions();
export default actions;

main.js mount生命周期中设置接收

image.png

子应用业务具体使用

<template>
  <h1>c1----首页---{{ state.username }}</h1>
  <button @click="change">换一个</button>
</template>

<script>
import actions from "./../qiankun/actions";
export default {
  data() {
    return {
      state:{}
    };
  },
  mounted() {
    // 接收state
    actions.onGlobalStateChange((state) => {
      console.log("state",state);
      this.state = state
    }, true);
  },
  methods
    change() {
      // 修改state
      actions.setGlobalState({ username:"热依扎" });
    },
  },
};
</script>

效果

image.png

image.png

生产

主应用 image.png 子应用

image.png

image.png

nginx

server {
  listen       8080;
  server_name  121.40.141.26;

  location / {
    root   /www/server/nginx/html/dist/dist;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  location /child/menus {
    root   /www/server/nginx/html/dist/dist;
    error_log  /www/server/nginx/logs/error.log debug;
    index  index.html index.htm;
    try_files $uri $uri/ /child/menus/index.html;
  }
}
include /www/server/panel/vhost/nginx/*.conf;
}

image.png