文前备注:
-
本文阿里 qiankun版本为2.x
-
本文vue项目皆以vue-cli 4.x 脚手架搭建。
-
本文react项目皆以create-react-app 5.x 脚手架搭建
-
本文仅为个人测试demo代码,非最优解
一、主应用配置
1、安装 qiankun
npm install -s qiankun
该命令为npm环境命令,其他环境自行变更
2、修改/src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#mainApp')
// 乾坤改造相关,为了便于观察,相关 import 单独写在相应代码附近,实际项目请按规范放置顶部
import {registerMicroApps,addGlobalUncaughtErrorHandler,initGlobalState} from "qiankun";
// 注册微应用
const config= [
//微应用1
{
name:"vue2-app1",
entry:"http://192.168.1.111:8181/", // 微应用访问地址
container:"#childAppContainer", // 主应用中的微应用容器
activeRule:"/childApp/vue2-app1", // 微应用在主应用路由中的前置路径,乾坤监听主应用中路由变化,匹配到对应路径,会请求>加载>渲染相应的微应用
props:{}, // 传递给微应用的参数
},
// 微应用2
{
name:"react-app1",
......
},
......
]
registerMicroApps(config);
// 添加全局的未捕获异常处理器
addGlobalUncaughtErrorHandler(e => {
const message = e.message||"";
const appName = message.replace(/[\w\s:]*?'([\d\w-_]*app[\d\w-_]*)'[\w\s_:]*/ig,`$1`);
let errorType = 'unknown';
if(/Failed to fetch/ig.test(message)){
setTimeout(()=>{
console.log('qk全局捕获异常处理器',`加载微应用【${errorData.appName}】失败,重定向404`);
router.replace({path:"/404"});
},100);
}
});
// 全局状态(主/微应用共享数据)
const state = {
token: store.state.token,
lang: store.state.lang,
};
// 初始化
const actions = initGlobalState(state);
// 主应用监听
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log("onGlobalStateChange", state, prev);
const { token } = state;
const changeToken = token !== store.state.token;
if (changeToken) {
console.log('检测到token变更,执行响应操作')
}
});
3、修改 /src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/childApp/*', // 通配符方便匹配微应用路径
name: 'childApp',
component: () => import( '../views/ChildApp.vue')
},
{
path: '/404',
name: 'page404',
component: () => import( '../views/System/404.vue')
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
const childrenPath = ["/childApp/vue2-app1"];// 微应用注册配置中相应的activeRule,实际项目中可以使用单独文件导出共享该配置参数
router.beforeEach((to, from, next) => {
console.log('router.beforeEach', `【${from.path}】 => 【${to.path}】`);
if(to.name){
if(to.name !== 'app') return next();
if(childrenPath.some(item => to.path.includes(item))) return next();
}
// 重定向404
return next({path:"/404"});
});
router.afterEach((to, from) => {
console.log('router.afterEach', `【${from.path}】 => 【${to.path}】`);
});
export default router
4、新增 /src/views/ChildApp.vue
<template>
<!--微应用容器-->
<div id="childAppContainer"></div>
</template>
<script>
import Vue from 'vue'
import { start } from 'qiankun'
export default {
mounted() {
// 启动乾坤
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
}
}
</script>
二、微应用 vue2.x + vue-router3.x 配置
1、新增 /src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2、修改 /src/router/index.js
import Vue from 'vue'
// import VueRouterfrom 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
export const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
]
// const router = new VueRouter({
// mode: 'history',
// base: process.env.BASE_URL,
// routes
// })
// export default router
3、修改 /src/main.js
import Vue from 'vue'
import App from './App.vue'
//import router from './router'
import store from './store'
Vue.config.productionTip = false
// new Vue({
// router,
// store,
// render: h => h(App)
// }).$mount('#app')
import {routes} from './router'
import VueRouter from 'vue-router'
const isQiankun = window.__POWERED_BY_QIANKUN__;
let router = null;
let instance = null;
function render(props) {
const {container} = props;
const base = isQiankun ? '/childApp/vue2-app1/' : process.env.BASE_URL;
router = new VueRouter({
mode: 'history',
base: base,
routes
});
router.beforeEach((to, from, next) => {
console.log("router.beforeEach",`【${from.path}】 => 【${to.path}】`);
next();
});
router.afterEach((to, from) => {
console.log("router.afterEach",`【${from.path}】 => 【${to.path}】`);
});
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app');
}
if(!isQiankun) {
render();
}
export async function bootstrap() {
console.log("qk.bootstrap");
}
export async function mount(props:ObjectType) {
console.log("qk.mount",props);
props.onGlobalStateChange((state,prev)=>{
console.log("onGlobalStateChange",state,prev);
const { token } = state;
if(token !== store.state.token) {
console.log("检测到token变更,执行后续操作")
}
},true);
render(props);
}
export async function unmount() {
console.log("qk.unmount");
if(instance) instance.$destroy();
if(instance) instance.$el.innerHTML = '';
instance = null;
router = null;
}
4、在/src同级目录下新增 vue.config.js
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
三、微应用react 17.x + react-router-dom 6.x 配置
1、新增 /src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2、修改 /src/index.js
import React from 'react';
// import {render} from 'react-dom';
import './index.css';
import {BrowserRouter,Route,Routes} from "react-router-dom";
import App from './App';
import Expenses from './view/expenses'
import Invoices from './view/invoices'
import InvoiceDetails from './view/invoiceDetails'
// const rootElement = document.getElementById("root");
// render(
// <BrowserRouter>
// <Routes>
// <Route path="/" element={<App/>}>
// <Route path="/expenses" element={<Expenses/>}/>
// <Route path="/invoices" element={<Invoices/>}>
// <Route path=":invoiceId" element={<InvoiceDetails/>}/>
// </Route>
// <Route path="*" element={
// <p>There's nothing here!</p>
// }/>
// </Route>
// </Routes>
// </BrowserRouter>,
// rootElement
// );
import {render as ReactRender,unmountComponentAtNode} from 'react-dom';
function getRootElement(container) {
return container ? container.querySelector('#root') : document.getElementById("root")
}
function render(props) {
const {container} = props;
const rootElement = getRootElement(container);
ReactRender(
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/childApp/react17-app2' : '/'}>
<Routes>
<Route path="/" element={<App/>}>
<Route path="/expenses" element={<Expenses/>}/>
<Route path="/invoices" element={<Invoices/>}>
<Route path=":invoiceId" element={<InvoiceDetails/>}/>
</Route>
<Route path="*" element={
<p>There's nothing here!</p>
}/>
</Route>
</Routes>
</BrowserRouter>,
rootElement
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('[react17] react app bootstraped');
}
export async function mount(props) {
console.log('[react17] props from main framework', props);
render(props);
}
export async function unmount(props) {
const {container} = props;
const rootElement = getRootElement(container);
unmountComponentAtNode(rootElement);
}
3、修改webpack打包配置、开发环境devServer配置
通过create-react-app 5.x搭建的create项目,你会发现没有对外开放webpakc的配置文件。
当需要自定义webapck配置时,有两个方案:
-
通过
create-react-app 5.x自身携带的eject命令转成自定义webpack工程,create-react-app会将webpack的配置拓印一个镜像到复制到项目中,并且所有的package.json命令都会被指向镜像的配置文件。因为
eject命令执行的操作不可逆,正常情况不推荐不熟悉webpack配置的开发者使用该方案。 -
通过插件
@rescripts/cli来实现create-react-app项目的webpack自定义配置。
下面,详细介绍这两种方案的具体实现
3.1、 通过 create-react-app 5.x 的 eject命令自定义webpack配置
-
执行
create-react-app 5.x的eject命令npm run eject该命令为npm环境命令,其他环境自行变更
命令执行完后,项目中可见
/config、/scripts文件夹,这两个文件夹中就是webpack的相关配置文件 -
新建
/config/qiankun.js文件(不建也行,建哪里都行)
const { name } = require('../package');
module.exports = {
devServer: {
port: 3001,
},
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`, // webpack 4
chunkLoadingGlobal: `webpackJsonp_${name}`, // webpack 5
globalObject: `window`,
}
}
-
修改
/config/webpack.config.js文件,新增两行代码const { output } = require('./qiankun'); // 新增代码 module.exports = function (webpackEnv) { return { output:{ ...output, // 新增代码 } } }若
create-react-app版本不同,代码可能稍有偏差,但只要在关键的module.exports.outpu出口配置处,新增对应的打包配置即可。 -
修改
/script/start.js文件const { devServer } = require('../config/qiankun'); // const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; const DEFAULT_PORT = parseInt(process.env.PORT, 10) || devServer.port || 3000;
3.2、 通过 @rescripts/cli插件自定义webpack配置
-
安装
@rescripts/clinpm install -d @rescripts/cli该命令为npm环境命令,其他环境自行变更
-
修改
package.json{ "scripts": { // "start": "react-scripts start", // "build": "react-scripts build", // "test": "react-scripts test", // "eject": "react-scripts eject", "start": "rescripts start", "build": "rescripts build", "test": "rescripts test" } } -
新增
.rescriptsrc.jsconst { name } = require('./package'); module.exports = { webpack: (config) => { config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; // config.output.jsonpFunction = `webpackJsonp_${name}`; // webpack 4 config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;// webpack 5 config.output.globalObject = 'window'; return config; }, devServer: (_) => { const config = _; // config.port = 3002; // create-react-app 5.x无效,请使用.env.POST config.headers = { 'Access-Control-Allow-Origin': '*', }; config.historyApiFallback = true; config.hot = false; // config.watchContentBase = false; // webpack 4 config.static = { watch:false }; // webpack 5 config.liveReload = false; return config; }, }; -
新增
.env.developmentPORT=3002