微前端——qiankun使用

qiankun是什么

qiankun 是一个基于single-spa实现的库。微前端的一种方案。具体可以去官网了解。

基本使用

基座

// main.js 添加以下配置
registerMicroApps([
    {
        name: 'reactApp',
        entry: '//localhost:8081',
        container: '#subContainer',
        activeRule: location => location.pathname.startsWith('/reactApp'),
    },
    {
        name: 'vue2App',
        entry: '//localhost:8082',
        container: '#subContainer',
        activeRule: '/vue2App',
    },
    {
        name: 'vue3App',
        entry: '//localhost:8083',
        container: '#subContainer',
        activeRule: '/vue3App',
    },
]);
// 启动 qiankun
start();
// 给子应用预留位置
<template>
    <div id="subContainer"></div>
</template>

子应用

vue2版本

// 在src目录下添加public-path.js文件并加入以下内容

if (window.__POWERED_BY_QIANKUN__) {

    // eslint-disable-next-line no-undef

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

}


import './public-path';// 放最前面!
import Vue from 'vue';
import App from './App.vue';
import router from './router/index';


Vue.config.productionTip = false;
Vue.use(ElementUI);
let instance = null;
function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}
// vue.config.js
const { defineConfig } = require('@vue/cli-service');
const {name} = require('./package').name;
module.exports = defineConfig({
  devServer: {
    port: 8082,
    headers: {
      'Access-Control-Allow-Origin': '*',
    }
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
})

vue3版本

配置基本与vue2一致,除了unmount方法。

export async function unmount() {
  instance.unmount();
  instance._container.innerHTML = '';
  instance = null;
}


react版本

public-path

// 在src目录下添加public-path.js文件并加入以下内容

if (window.__POWERED_BY_QIANKUN__) {

    // eslint-disable-next-line no-undef

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

}


入口文件 index.js

import './public-path';
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

let root = null;

function render(props = {}) {
  const { container, router: mainRouter } = props;
  root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
  React.mainRouter = mainRouter;
  root.render(
    <React.StrictMode>
      <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/reactApp' : '/'}>
        <App />
      </BrowserRouter>
    </React.StrictMode>
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  root.unmount();
}

webpack配置

如果使用create-react-app创建react项目,reactwebpack配置隐藏到node-module目录里,导致我们难以修改webpack配置。

有两种方法可以解决:执行yarn eject命令将隐藏的webpack配置弹出;使用第三方库(推荐)。

下面我使用第三方库craco,具体配置可以参考官方文档

安装

npm i -D @craco/craco

在根目录下创建一个craco.config.js文件。

const { name } = require('./package');


module.exports = {
    webpack: {
        configure(config) {
            config.output.library = `${name}-[name]`;
            config.output.libraryTarget = 'umd';
            config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
            config.output.globalObject = 'window';
            return config;
        }
    },
    devServer: {
        port: 8081,
        open: false,
        headers: {
            'Access-Control-Allow-Origin': '*',
        }
    },
};

改变package.json文件scripts属性值。

// package.json
"scripts": {
-  "start": "react-scripts start"
+  "start": "craco start"
-  "build": "react-scripts build"
+  "build": "craco build"
-  "test": "react-scripts test"
+  "test": "craco test"
}

应用间跳转

参考链接: qiankun微应用之间、主微应用之间相互跳转方式总结与实践

  1. 通过window.history.pushState(state, title, url)方式跳转。

    state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。

    title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。

    url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

const state = { page_id: 1, user_id: 5 };
const url = "hello-world.html";
history.pushState(state, null, url);
  1. 通过props属性传递主应用路由实例给子应用,子应用通过调用路由实例方法完成跳转。

应用间通信

  1. 官方提供的initGlobalState。提供了三个方法initGlobalStateonGlobalStateChangesetGlobalState。调用setGlobalState会触发onGlobalStateChange方法。
import { initGlobalState } from 'qiankun';


// 初始化state
const actions = initGlobalState({});


// 监听变化
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log('主应用检测到state变更:', state, prev);
});

// 改变state
actions.setGlobalState({num: 3});

// 将actions通过props传递给子应用,子应用拿到方法后可以为actions添加onGlobalStateChange方法,也可以监听到state的改变
export default actions;

但是这个方法在使用的时候,控制台会出现一个警告,globalState将会被移除在qiankun3.0
image.png
2. 使用vuex或者redux。很简单,就是将创建好的store通过props传递给子组件。

总结

其实这几种无非就是通过props去传递一个对象或者方法,在子应用去使用调用这些方法属性。

样式冲突解决方法

qiankun提供的sandbox

配置 { sandbox : { strictStyleIsolation: true } } ,实现形式为将整个子应用放到原生的Shadow DOM内进行嵌入,完全隔离了主子应用。

// 启动 qiankun

start({

    prefetch: true,// 预加载子资源

    sandbox: {

        strictStyleIsolation: true
    }

});

image.png

// 创建一个sandbox
const app = document.getElementById('app')
const shadow = app.attachShadow('mode: open');

缺点:完全隔离,子应用无法使用父应用的全局样式,父应用也无法拿到子应用的dom。如果需要抽离公共组件库,子应用构建后,无法用到父应用的组件库。

qiankun提供的实验性沙箱

添加experimentalStyleIsolation: true属性,qiankun会自动为子应用所有的样式增加后缀标签,如:div[qiankun-child]。相当于vue中的scoped。如下图。
image.png

// 启动 qiankun

start({

    prefetch: true,// 预加载子资源

    sandbox: {

        experimentalStyleIsolation: true// 实验性沙箱
    }

});

但是其实我们的应用一般来说都是vue或者reactvuereact都有自己样式组件化的方法了,所以使用vuereact样式组件化能解决绝大部分样式污染的问题。

原理

监听路由变化

若是hash路由通过hashchange原生事件监听并拿到变化后的hash。若是history路由则通过popstate原生事件监听浏览器的后退前进,还需改写window.history.pushStatewindow.history.replaceState,以便拿到变化后url

丐版实现

let prePath = window.location.pathname;// 保存上一个路由路径,用于卸载上一个子应用
let curPath = '';
window.addEventListener('popstate', () => {
    prePath = curPath;
    curPath = window.location.pathname;
    handleRoute();// 拿到路由后匹配路由
}
const originPushState = window.history.pushState;
window.history.pushState = (...arg) => { pathChange(originPushState, ...arg); };
const originReplaceState = window.history.replaceState;
window.history.replaceState = (...arg) => { pathChange(originReplaceState, ...arg); };
function pathChange(fn, ...arg) {
    fn.apply(window.history, arg);
    prePath = curPath;
    curPath = window.location.pathname;
    handleRoute();// 拿到路由后匹配路由
}

匹配子应用

通过遍历配置项得到其中的activeRule与当前路由进行匹配,匹配成功加载路由。

加载子应用

  1. 拿到匹配路由的entry,并通过fetch获取其中内容,拿到html文本;
  2. import-html-entry解析html,拿去script标签的
  3. 通过eval执行script标签的内容;
  4. 通过umd模块获取子应用暴露出来的生命周期,调用子应用mount方法,完成子应用加载。

渲染子进程

html插入预留的container中。

参考链接

  1. qiankun官网
  2. qiankun微应用之间、主微应用之间相互跳转方式总结与实践
  3. craco官网

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYb8G8KJ' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片