乾坤:
目标是将庞大的单体前端应用,拆解成多个可独立开发、部署、运行的小型应用(微应用),并最终无缝集成在一起
-
主应用(基座)
- 负责注册、加载、卸载子应用。
- 提供公共布局、登录、全局样式、全局状态。
-
子应用(微应用)
- 一个完整的业务模块(如:商品、订单、用户中心)。
- 暴露固定生命周期钩子,供主应用调用
实操:
一、主应用
1、main.js 注册子应用
js
import { registerMicroApps, start } from 'qiankun';
const props = {
getMainData: () => store.state.globalState.mainData,
updateMainData:(child)=>{
store.commit('SET_GLOBAL_STATE',child)
}
}
registerMicroApps([
{
name: 'vue app',
entry: '//localhost:8000',
container: '#childContainer',
activeRule: '/vue',
props
},
{
name: 'react app', // app name registered
entry: '//localhost:9000',
container: '#childContainer',
activeRule: '/react',
props
}
]);
start({
sandbox: {
strictStyleIsolation: true, // 严格样式隔离(推荐),主子应用样式隔离
// experimentalStyleIsolation: true // 可选:追求兼容(弹窗、UI 库正常)
}
})
2、App.vue (任意组件,存放子应用容器)
js
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/vue">跳转子应用vue</router-link> |
<router-link to="/react">跳转子应用react</router-link> |
</nav>
<div>
<h1>主应用data</h1>
<h2 style="color:red">name:{{ name}}</h2>
</div>
<router-view/>
<hr>
<div id="childContainer"></div> // 存放子应用容器
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['globalState']),
name(){
return this.globalState?.mainData?.userInfo?.name||''
}
}
}
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
.childContainer{
display: flex;
justify-content: center;
}
</style>
二、子应用-vue
1、main.js
js
// 定义变量存储 Vue 实例
let instance = null
// 渲染函数
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: h => h(App)
// 乾坤会把容器传给你,避免挂载到主应用根节点(不污染主应用节点)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时(非微应用环境)直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export async function bootstrap() {
// console.log('[vue] 微应用初始化')
}
export async function mount(props) { // props主应用传递的公共数据
store.commit('SET_GLOBAL_STATE',{
...props,
mainData:props?.getMainData()||{}
})
render(props)
}
export async function unmount() {
console.log('[vue2] 微应用卸载')
instance.$destroy() // 销毁实例
instance.$el.innerHTML = '' // 清空 DOM
instance = null
}
说明:为什么主应用传递给子应用时,子应用能拿到 container.querySelector('#app') ,主应用时如何能识别到的?
当子应用被主应用加载时,qiankun 会自动做这一步:
- 去请求子应用的 index.html
- 解析子应用 HTML,隔离后挂载 到主应用容器(包括里面的
<div id="app"></div>) - 子应用的根节点 #app 渲染到主应用的 #childContainer 容器中
2、vue.config.js(子应用能被主应用识别加载)
js
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json')
module.exports = defineConfig({
// 微应用唯一名称(主应用注册时要一致)
configureWebpack: {
output: {
library: `${name}-[name]`, // 主应用上name呼应
libraryTarget: 'umd', // 把微应用打包成 umd 格式,让子应用变成"可被主应用加载的格式"
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
},
transpileDependencies: true,
devServer: {
port: 8000, // 自己定义微应用端口
headers: {
'Access-Control-Allow-Origin': '*', // 允许跨域(乾坤必须)
},
},
})
3、router/index.js
js
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : process.env.BASE_URL, // vue 是主应用配置的 activeRule
routes
})
说明:为什么vue不需要改publicPath?
-
Vue CLI 项目默认
publicPath: '/' -
被 qiankun 加载时,会自动修正子应用静态资源路径
-
但子应用部署到非根目录时必须改,不是永远不用改
三、子应用-react
1、index.js
js
let instance = null
function render(props = {}) {
const { container } = props
const domContainer = container
? container.querySelector('#root')
: document.getElementById('root')
instance = ReactDOM.createRoot(domContainer)
instance.render(
<React.StrictMode>
{/* 必须包 Provider */}
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
</React.StrictMode>
)
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export async function bootstrap() {
console.log('[react] 微应用初始化')
}
export async function mount(props) {
store.dispatch({
type: "SET_GLOBAL_STATE",
payload: {
...props,
mainData: props?.getMainData?.() || {}
}
})
render(props)
}
export async function unmount() {
if (instance) {
instance.unmount()
instance = null
}
}
reportWebVitals();
2、craco.config.js
react脚手架默认是这样的:
- 所有 webpack、babel、eslint 配置全部藏在 node_modules 里
- 你看不到、改不了
- 你的项目很干净,只有 src、public
官方eject方法:
-
不可逆:一旦执行,再也回不去
- 把所有隐藏的配置文件,一次性全部复制到你的项目里
- 这个命令会被删掉,再也不能执行第二次,也不能撤销!
-
暴露几百个配置文件,你必须自己维护所有依赖和更新
- 暴露几百个配置文件,必须自己维护依赖
-
失去 CRA 后续升级能力
- CRA 官方会不断更新,但eject就没有了(比如:优化打包速度、修复安全漏洞、升级 webpack、升级 babel、升级 eslint、加新特性等)
craco不用 eject,也能改 webpack 配置:
js
const { name } = require('./package.json')
module.exports = {
webpack: {
configure: (config) => {
config.output.library = `${name}-[name]`
config.output.libraryTarget = 'umd'
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
config.output.publicPath = process.env.NODE_ENV === 'development'
? 'http://localhost:9000/'
: '/'; // 方便引入静态资源不会404
return config
}
},
devServer: (config) => {
config.headers = {
'Access-Control-Allow-Origin': '*'
}
return config
}
}
3、router/index
js
import { createBrowserRouter } from 'react-router-dom'
import App from "../App.js"
// 👇 核心:微应用必须加这个 base
const base = window.__POWERED_BY_QIANKUN__ ? '/react' : '/'
const router = createBrowserRouter([
{
path: '/',
element:<App />
}
], {
basename: base // 👈 这里注入 base
})
export default router
四、主、子通信
1、vuex+props
-
将公共数据、更新公共数据方法存储到
vuex中 -
通过注册应用registerMicroApps中
props传递给子数据jsconst props = { getMainData: () => store.state.globalState.mainData, updateMainData:(child)=>{ store.commit('SET_GLOBAL_STATE',child) } } registerMicroApps([ { name: 'vue app', entry: '//localhost:8000', container: '#childContainer', activeRule: '/vue', props } ]); -
子应用通过周期函数
mount获取props再另行存储
2、initGlobalState、setGlobalState、onGlobalStateChange
- initGlobalState(数据初始化)
- setGlobalState(更新数据)
- onGlobalStateChange(监听数据变化)
js
// qiankun/index.js
import { initGlobalState } from 'qiankun';
const initialState = {
userInfo: {},
token:''
}
// 生成 actions
const actions = initGlobalState(initialState)
// 监听全局变化(可选)
actions.onGlobalStateChange((state) => {
console.log('主应用全局状态变化:', state)
})
export { actions }
js
// main.js
import "./qiankun"
js
// 组件内使用
import { actions } from '@/qiankun/index.js'
onChangeGlobal(){
actions.setGlobalState({token:`token_update_----`})
}
js
// 子应用中使用
// 子应用通过props接收,方法都在props上可以直接调用
props.setGlobalState({token:'00000000000000000000'})
五、子、子通信
需要主应用做中转
- initGlobalState主应用
- 子应用A:setGlobalState
- 子应用B:onGlobalStateChange监听获取
总结:
主应用、子应用相连:
1、主应用做什么
-
注册子应用(
registerMicroApps) -
启动 qiankun(
start) -
提供子应用挂载容器(
<div id="container"></div>) -
通过
activeRule路由规则匹配子应用
2、子应用做什么
-
子应用在主应用提供的容器内进行渲染
-
导出生命周期函数 (
bootstrap/mount/unmount) -
配置 webpack 打包为 umd 格式(让主应用能识别)
librarylibraryTarget: 'umd'chunkLoadingGlobal
-
配置跨域 (
devServer.headers) -
配置路由 base (与主应用
activeRule对应) -
配置 publicPath(防止静态资源 404)
- React 必须配
- Vue 可配可不配(建议配)