Appearance
微前端qiankun 使用
多框架集成、子应用并行运行、跨应用通信
主应用注册微应用并启动
npm i qiankun -Simport { registerMicroApps, start } from 'qiankun'
import type { MicroAppStateActions } from 'qiankun'
interface SharedProps {
token?: string;
actions?: MicroAppStateActions;
shareMainApp?: typeof sharedComponent;
}
const apps = [
{
name: 'vue3-project', // 子应用名称
entry: 'http://localhost:8101/', // 子应用入口
container: '#subAppContainer', // 放子应用的容器
activeRule: '/vue3-project', // 激活子应用的路路由
props: { // 传递给子应用的数据
token: 'abcd23456',
actions
}
},
{
name: 'react18-project',
entry: 'http://localhost:8102/',
container: '#subAppContainer',
activeRule: '/react18-project',
props: {
shareMainApp: sharedComponent
}
},
{
name: 'umi-project',
entry: 'http://localhost:8103/',
container: '#subAppContainer',
activeRule: '/umi-project'
}
]
registerMicroApps<SharedProps>(apps, {
beforeLoad: [async app => console.log('before load', app.name)],
beforeMount: [async app => console.log('before mount', app.name)],
afterMount: [async app => console.log('after mount', app.name)]
})
start({
// prefetch: true, // 开启预加载,在浏览器空闲时,预先加载其他子应用的资源
// prefetch: 'all', // 预加载所有子应用
// prefetch: ['vue3-project', 'umi-project'], // 只预加载指定的应用
// prefetch: (apps) => apps.filter(app => app.name !== 'test-app'),
singular: false, // 多实例子应用共存
sandbox: {
// strictStyleIsolation: false, // CSS 严格隔离,每个子应用被包裹在 Shadow DOM 中,样式完全隔离
experimentalStyleIsolation: true // CSS 实验性隔离,Scoped CSS,qiankun 会给子应用的所有样式添加特殊前缀
}
})主应用设置跳转子应用的入口
const routes = createBrowserRouter([
{
path: '/',
element: <LayoutPage />,
children: [
{
index: true, // 默认打开
element: <Home />
},
{
path: 'vue3-project/?/*', // 通配符匹配 /vue3-project、 /vue3-project/goodsDetail
element: <EmptyPage />
},
{
path: 'react18-project/?/*',
element: <EmptyPage />
},
{
path: 'umi-project/?/*',
element: <EmptyPage />
}
]
}
])主应用设置子应用挂载的容器
const EmptyPage = () => {
return <div id="subAppContainer"></div>
}
export default EmptyPagevite 构建的子应用(vue3、 react18)
qiankun 不支持 vite,需要使用插件 vite-plugin-qiankun
npm i vite-plugin-qiankun -Simport qiankun from 'vite-plugin-qiankun'
export default defineConfig({
plugins: [
vue(),
qiankun('vue3-project', { // 与主应用注册的name保持一致
useDevMode: true
})
],
server: { // 本地启动
port: 8101,
cors: true, // 允许跨域,主应用才能访问
origin: "//localhost:8101", // 不加这个静态资源404
headers: {
// 允许主应用访问子应用的资源(CORS 配置)
'Access-Control-Allow-Origin': '*'
}
},
...
})export default defineConfig({
plugins: [
// react(),
qiankun('react18-project', {
useDevMode: true
})
],
server: {
port: 8102,
headers: { 'Access-Control-Allow-Origin': '*' }
},
...
})vue3+vite 子应用路由
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
// 路由前缀
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/vue3-project/' : '/'),
routes: [
{
path: '/',
name: 'home',
meta: {
title: '首页'
},
component: HomeView
}
...
})react18+vite 子应用路由
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const routes = createBrowserRouter([
{
path: '/',
element: <LayoutPage />,
children: [
{
index: true,
element: <Home />
},
{
path: 'article',
element: <Article />,
children: [
{
path: 'list',
element: <ArticleList />
},
...
]
},
...
]
},
{
path: '*',
element: <NotFound />
}
], { basename: qiankunWindow.__POWERED_BY_QIANKUN__ ? '/react18-project' : '/' }) // 路由前缀vue3+vite 子应用 main.js 添加生命周期
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let app = null
function render() {
app = createApp(App)
app.use(router)
app.mount('#subApp')
}
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render()
}
renderWithQiankun({
mount(props) {
console.log('props', props)
localStorage.setItem('xhhToken', props.token)
render()
},
update() {},
bootstrap() {},
unmount() {
app.unmount()
}
})react18+vite 子应用 main.tsx 添加生命周期
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let root = null
function render() {
root = createRoot(document.getElementById('root1')!)
root.render(
<ConfigProvider locale={zhCN}
theme={{
token: {
colorPrimary: '#990ff5'
}
}}>
<RouterProvider router={router}></RouterProvider>
</ConfigProvider>
)
}
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render()
}
renderWithQiankun({
mount(props) {
render()
},
update() {},
bootstrap() {},
unmount() {
root.unmount()
}
})umi 子应用配置
.env文件:
PORT=8103config/config.ts:
import { defineConfig } from "umi"
// webpack相关的配置
export default defineConfig({
base: '/umi-project',
plugins: ['@umijs/plugins/dist/qiankun'],
qiankun: {
slave: {}
},
...
});src/app.ts:
暴漏qiankun生命周期
export const qiankun = {
async bootstrap() {
console.log('umi app ------ bootstrap')
},
async mount(props: any) {
console.log('umi app ------ mount')
console.log(props)
},
async unmount() {
console.log('umi app ------ unmount')
}
}react19+webpack5 子应用配置
.env文件:
BROWSER=none
PORT=8104或者
module.exports = {
devServer: {
port: 8104
},
webpack: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
configure: (webpackConfig, { env, paths }) => {
webpackConfig.output.library = "webpack-react";
webpackConfig.output.libraryTarget = "umd";
...
return webpackConfig;
},
}
}路由前缀:
const router = createBrowserRouter([
{
path: '/',
element: <Demo />
},
{
path: '*',
element: <div>404</div>
}
], { basename: window.__POWERED_BY_QIANKUN__ ? '/create-react19-app' : '/' })webpack配置:
webpackConfig.output.library = "webpack-react"
webpackConfig.output.libraryTarget = "umd"public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}添加生命周期:
import './public-path.js'
let root = null
function render() {
root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<RouterProvider router={routerConfig} />
</Provider>
);
}
// 判断是否在qiankun环境下
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
// 在微应用初始化的时候调用一次,下次重新进入微应用时会直接进入 mount
export async function bootstrap() {
console.log("react19 app --- bootstrap")
}
export async function mount(props) {
console.log("react19 app --- mount")
render(props)
}
export async function unmount(props) {
console.log("react19 app --- unmount")
root?.unmount()
}vue2+webpack3 子应用配置
设置端口号:
devServer: {
port: 8105,
headers: {
'Access-Control-Allow-Origin': '*' // 子应用允许跨域
}
}路由前缀:
export default new Router({
base: window.__POWERED_BY_QIANKUN__ ? '/vue2-project' : '/',
...
routes: [
...
]
})webpack配置:
output: {
library: 'vue2-project',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_vue2`,
publicPath: 'http://localhost:8105/' // 设置这个就不需要引入 public-path.js 了
...
},
}public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line camelcase, no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
// __webpack_public_path__ 值是:http://localhost:8105/
}添加生命周期:
...
import './public-path.js'
let instance = null
function render () {
instance = new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
}
// 是否从qiankun主应用进入
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
export async function bootstrap () {
console.log('vue2 --- bootstrap')
}
export async function mount (props) {
console.log('vue2 --- mount')
render(props)
}
export async function unmount (props) {
console.log('vue2 --- unmount')
instance.$destroy()
instance = null
}dva 子应用配置
设置端口号:
scripts": {
"dev": "cross-env BROWSER=none PORT=8107 SOCKET_SERVER=none roadhog server",
...
},路由前缀:
import createHistory from 'history/createBrowserHistory'
function render() {
app = dva({
history: createHistory({ // history路由模式
basename: window.__POWERED_BY_QIANKUN__ ? '/dva-project/' : '/'
})
})
...
}新建 webpack.config.js:
module.exports = (webpackConfig, env) => {
webpackConfig.output.library = "dva-project";
webpackConfig.output.libraryTarget = "umd";
webpackConfig.output.jsonpFunction = "webpackJsonp_dva";
return webpackConfig
}public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line camelcase, no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}添加生命周期:
import dva from 'dva';
import createHistory from 'history/createBrowserHistory'
...
import './public-path.js'
let app = null
function render() {
app = dva({
history: createHistory({
basename: window.__POWERED_BY_QIANKUN__ ? '/dva-project/' : '/'
})
});
window.dvaApp = app
...
app.start('#rootdva')
}
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
export async function bootstrap() {
console.log('dva--- bootstraped')
}
export async function mount(props) {
console.log('dva--- mount')
render(props)
}
export async function unmount() {
console.log('dva--- unmount')
app = null
window.dvaApp = null
}子应用跳到其他应用
// 跳到其他子应用
window.history.pushState({}, '', '/react18-project/article/list')
// 跳到主应用
window.history.pushState({}, '', '/');启动应用
{
"name": "xhh-qiankun-project",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "npm-run-all --parallel dev:*",
"dev:main": "npm run dev --workspace main-app",
"dev:vue": "npm run dev --workspace vue3-project",
"dev:react": "npm run dev --workspace react18-project",
"dev:umi": "npm run dev --workspace umi-project"
},
"workspaces": [
"main-app",
"vue3-project",
"react18-project",
"umi-project"
],
"devDependencies": {
"npm-run-all": "^4.1.5"
},
"dependencies": {
"cheerio": "^1.0.0-rc.10",
"vite-plugin-qiankun": "^1.0.15"
}
}共享组件
主应用组件
import CommonComp from '@/components/CommonComp'
const sharedComponent = {
CommonComp
}
registerMicroApps([
{
name: 'react18-project',
entry: 'http://localhost:8102/',
container: '#subAppContainer',
activeRule: '/react18-project',
props: {
shareMainApp: sharedComponent
}
}
...
])子应用设置共享组件
utils/shareMainComp.ts:
let shareMainComponent = {}
interface ShareMainComponentResult {
CommonComp: React.ComponentType;
}
export const getShareMainComponent = () => {
return shareMainComponent as ShareMainComponentResult
}
export const setShareMainComponent = (currShareMainApp) => {
for (const key in currShareMainApp) {
if (Object.prototype.hasOwnProperty.call(currShareMainApp, key)) {
shareMainComponent[key] = currShareMainApp[key]
}
}
}import { setShareMainComponent } from '@/utils/shareMainComp'
renderWithQiankun({
mount(props) {
setShareMainComponent(props.shareMainApp)
render()
},
...
})子应用使用组件:
import { getShareMainComponent } from '@/utils/shareMainComp'
function Home() {
usePageTitle('首页')
const { CommonComp } = getShareMainComponent()
...
}
<CommonComp />应用通信
1. initGlobalState
主应用
initGlobalState设置全局状态:
import { initGlobalState, MicroAppStateActions } from 'qiankun'
const initialState = {}
const actions: MicroAppStateActions = initGlobalState(initialState)
export default actions传递给子应用:
import actions from '@/utils/actions'
registerMicroApps([
{
name: 'vue3-project',
entry: 'http://localhost:8101/',
container: '#subAppContainer',
activeRule: '/vue3-project',
props: {
actions
}
},
...
])主应用修改全局状态:
import actions from '@/utils/actions'
function changeTheme(){
if(theme === 'dark'){
setTheme('light')
actions.setGlobalState({ theme: 'light' })
}else{
setTheme('dark')
actions.setGlobalState({ theme: 'dark' })
}
}子应用
先配置一个空的 actions 实例:
class Actions {
actions = { onGlobalStateChange: () => {}, setGlobalState: () => {} }
setActions(actions) { this.actions = actions }
onGlobalStateChange(...args) { return this.actions.onGlobalStateChange(...args) }
setGlobalState(...args) { return this.actions.setGlobalState(...args) }
}
let actions = new Actions()
export default actions注入 actions:
renderWithQiankun({
mount(props) {
actions.setActions(props.actions)
render()
},
unmount(props) {
// 取消监听,避免内存泄露
props.actions.offGlobalStateChange()
app.unmount()
}
...
})监听全局状态:
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态改变:', state, prev)
}, true) // 第二个参数设为true, 初始化时立即触发一次2. 自定义事件通信
// 应用1 发送事件
window.dispatchEvent(new CustomEvent('app-message', {
detail: { msg: 'message from App A' }
}))
// 应用2 接收事件
window.addEventListener('app-message', (e) => {
console.log(e.detail.msg)
})3. props
4. storage
效果图

参考文档