qiankun学习笔记

96 阅读3分钟

qiankun学习记录

微前端是什么qiankun官网是这么介绍的

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。 微前端架构具备以下几个核心价值:

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

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

  • 增量升级

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

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

  • 通俗的讲就是将不同框架所产生的项目进行一个整合,使得在访问时可通过一个主应用进入不同的子应用,子应用可以是vue/react/angular

开始

首先我们比如说有三个项目他们如下

image.png base作为主应用用的vue3 两个子应用为react项目vue2项目

  • 首先在应用中下载qiankun
$ yarn add qiankun # 或者 npm i qiankun -S

image.png

应用配置

主应用配置

主应用main.js中加入如下配置

 /* eslint-disable */
import { createApp } from 'vue'
import App from './App.vue'
//引入 qiankun
import { registerMicroApps, start } from "qiankun"

// import "./registerApp" 也可以写个文件引入
import { createRouter, createWebHistory } from "vue-router"
import route from "@/route/index"
const router = createRouter({
    history: createWebHistory(),
    routes: route
})

createApp(App).use(router).mount('#app')
//qiankun 配置
registerMicroApps([//注册子应用
    {
        name: 'vue2-app',
        entry: '//localhost:8081',//注意路径端口 如果路由模式带#记得带#
        container: '#container',//此为为子应用提供的容器
        activeRule: '/app-vue2',//子应用路由
    },
    {
        name: 'react-app',//同上
        entry: '//localhost:8082',
        container: '#container',
        activeRule: '/app-react',
    }
], {//此处为子应用的生命周期
    beforeLoad: (app) => console.log('应用加载', app.name),
    beforeMount: (app) => console.log('挂载前', app.name),
    afterMount: (app) => console.log('挂载后', app.name),
    beforeUnmount: (app) => console.log('卸载前', app.name),
    afterUnmount: (app) => console.log('卸载后', app.name),
})
//启动
start();

vue.config.js文件中进行如下配置

devServer: {
//headers 设置可跨域
    headers: {
      'Access-Control-Allow-Origin': '*',	// 设置允许跨域请求,否则会因为在其他端口号获取资源报错
    },
    port: 8080,	// 设置每次打开本地的端口号
  },

现在我们的主应用配置就已经完了 在页面中可以使用vue-router方式进行跳转

<div>
<!--此处路由为main.js中 activeRule的配置-->
    <router-link to="/app-vue2">app-vue2</router-link>
  </div>
  <div>
    <router-link to="/app-react">app-react</router-link>
  </div>
  <div id="container"></div>

子应用vue2-app配置

src新建public-path.js文件

/* eslint-disable */
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }

main.js修改如下

import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
Vue.use(VueRouter) //qiankun官网的示例 <router-link/>不生效我这里直接初始化下
Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
  //这里判断的是是否是从主应用进入如果是则带入/app-vue2/前缀
  //不然路由则不在子应用下,会再次访问到主应用那里
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue2/' : '/',
    mode: 'history',
    routes,
  });
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
  //若无container则说明是单独访问非从主应用进入
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
//协议
export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
//此处会传入props内部带有渲染的容器也就是主应用的#container
//使用render函数创建应用方便子应用卸载时销毁,不然占位不拉屎
  console.log('[vue] props from main framework', props);
  render(props);
  
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
  //销毁
}

vue.config.js更改如下

const { name } = require('./package');
module.exports = {
  devServer: {
    port:8081,
    //设置可跨域
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式 默认是systemjs
      // jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

子应用 react-app 配置

官网使用的插件是 @rescripts/cli 我这里使用的是 react-app-rewired

yarn add react-app-rewired -D 

下载完之后更改package.json文件运行配置

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  }

记得改端口号我这里是8082 .env文件 PORT=8082

最外层新增config-overrides.js文件

const { name } = require('./package.json')
console.log(name)
 
module.exports = {
	webpack: function (config, env) {
		if (Array.isArray(config.entry)) {
			config.entry = config.entry.filter(
				(e) => !e.includes('webpackHotDevClient')
			)
		}
		// config.entry = config.entry.includes('webpackHotDevClient')
 
		config.output.library = `${name}-[name]`
		config.output.libraryTarget = 'umd'
		config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
                    // 写项目启动的源,否则图片无法显示
		config.output.publicPath = '//localhost:8082/'
		return config
	},
	devServer: (configFunction) => {
		return function (proxy, allowedHost) {
			const config = configFunction(proxy, allowedHost)
			config.open = false
			config.hot = false
                        //设置可跨域
			config.headers = {
				'Access-Control-Allow-Origin': '*',
			}
			// Return your customised Webpack Development Server config.
			return config
		}
	},
}

src内新增文件public-path.js

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

修改index.js文件

import './public-path';
import React from 'react';
import App from './App';
//import ReactDOM from 'react-dom'; 16版本
import ReactDOM from 'react-dom/client';//18版本

//这里官网给的是16,我当前下的是18
let root = null
function render(props) {
  const { container } = props;
  //18 版本 ReactDOM.render弃用了改为 createRoot
  root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
  //16 版本
  // ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}

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) {
  const { container } = props;
  root.unmount(container ? container.querySelector('#root') : document.querySelector('#root'));//18版本
 // ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));16版本
}

然后运行三个项目

主应用

image.png

子应用vue2-app

image.png

子应用react-app

image.png

单独访问

vue2-app

image.png

react-app

image.png