qiankun
qiankun 是基于 single-spa 的微前端实现框架,开箱即用。
核心原理
主应用(基座)
│
├─ registerMicroApps([
│ { name, entry, container, activeRule }
│ ])
│
├─ start({ sandbox, prefetch, ... })
│
└─ 子应用生命周期:bootstrap → mount → unmount主应用(基座)
│
├─ registerMicroApps([
│ { name, entry, container, activeRule }
│ ])
│
├─ start({ sandbox, prefetch, ... })
│
└─ 子应用生命周期:bootstrap → mount → unmount快速开始
主应用
js
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:7101',
container: '#sub-app-container',
activeRule: '/app/vue',
},
{
name: 'react-app',
entry: '//localhost:7102',
container: '#sub-app-container',
activeRule: '/app/react',
},
])
start({
sandbox: { experimentalStyleIsolation: true },
prefetch: 'all',
})import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:7101',
container: '#sub-app-container',
activeRule: '/app/vue',
},
{
name: 'react-app',
entry: '//localhost:7102',
container: '#sub-app-container',
activeRule: '/app/react',
},
])
start({
sandbox: { experimentalStyleIsolation: true },
prefetch: 'all',
})子应用(以 Vue3 为例)
js
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
let app = null
let router = null
export async function bootstrap() {
console.log('子应用 bootstrap')
}
export async function mount(props) {
const { container } = props
router = createRouter({
history: createWebHistory('/app/vue'),
routes: [],
})
app = createApp(App)
app.use(router)
app.mount(container ? container.querySelector('#app') : '#app')
}
export async function unmount() {
app?.unmount()
app = null
router = null
}// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
let app = null
let router = null
export async function bootstrap() {
console.log('子应用 bootstrap')
}
export async function mount(props) {
const { container } = props
router = createRouter({
history: createWebHistory('/app/vue'),
routes: [],
})
app = createApp(App)
app.use(router)
app.mount(container ? container.querySelector('#app') : '#app')
}
export async function unmount() {
app?.unmount()
app = null
router = null
}子应用 webpack 配置
js
// vue.config.js
module.exports = {
publicPath: '//localhost:7101',
configureWebpack: {
output: {
library: 'vue-app',
libraryTarget: 'umd',
chunkLoadingGlobal: 'webpackJsonp_vue-app',
},
},
devServer: {
port: 7101,
headers: { 'Access-Control-Allow-Origin': '*' },
},
}// vue.config.js
module.exports = {
publicPath: '//localhost:7101',
configureWebpack: {
output: {
library: 'vue-app',
libraryTarget: 'umd',
chunkLoadingGlobal: 'webpackJsonp_vue-app',
},
},
devServer: {
port: 7101,
headers: { 'Access-Control-Allow-Origin': '*' },
},
}沙箱机制
快照沙箱(LegacySandBox)
js
// 原理:在 mount 时快照 window,unmount 时还原
class SnapshotSandbox {
constructor() {
this.windowSnapshot = {}
this.modifiedProps = {}
}
active() {
this.windowSnapshot = {}
for (const key in window) {
this.windowSnapshot[key] = window[key]
}
}
inactive() {
for (const key in window) {
if (window[key] !== this.windowSnapshot[key]) {
this.modifiedProps[key] = window[key]
window[key] = this.windowSnapshot[key]
}
}
}
}// 原理:在 mount 时快照 window,unmount 时还原
class SnapshotSandbox {
constructor() {
this.windowSnapshot = {}
this.modifiedProps = {}
}
active() {
this.windowSnapshot = {}
for (const key in window) {
this.windowSnapshot[key] = window[key]
}
}
inactive() {
for (const key in window) {
if (window[key] !== this.windowSnapshot[key]) {
this.modifiedProps[key] = window[key]
window[key] = this.windowSnapshot[key]
}
}
}
}代理沙箱(ProxySandBox)
js
// 原理:通过 Proxy 拦截 window 操作,支持多实例
class ProxySandbox {
constructor() {
const fakeWindow = Object.create(null)
const proxy = new Proxy(fakeWindow, {
set(target, key, value) {
target[key] = value
return true
},
get(target, key) {
return target[key] || window[key]
},
})
this.proxy = proxy
}
}// 原理:通过 Proxy 拦截 window 操作,支持多实例
class ProxySandbox {
constructor() {
const fakeWindow = Object.create(null)
const proxy = new Proxy(fakeWindow, {
set(target, key, value) {
target[key] = value
return true
},
get(target, key) {
return target[key] || window[key]
},
})
this.proxy = proxy
}
}通信方案
js
// 主应用
import { initGlobalState } from 'qiankun'
const actions = initGlobalState({ token: '' })
actions.onGlobalStateChange((state, prev) => {})
actions.setGlobalState({ token: 'xxx' })
// 子应用
export async function mount(props) {
props.onGlobalStateChange((state, prev) => {}, true)
props.setGlobalState({ token: 'xxx' })
}// 主应用
import { initGlobalState } from 'qiankun'
const actions = initGlobalState({ token: '' })
actions.onGlobalStateChange((state, prev) => {})
actions.setGlobalState({ token: 'xxx' })
// 子应用
export async function mount(props) {
props.onGlobalStateChange((state, prev) => {}, true)
props.setGlobalState({ token: 'xxx' })
}常见问题
1. 样式隔离
experimentalStyleIsolation: true— 添加div[data-qiankun-xxx]前缀- 子应用使用 CSS Modules / Scoped
2. 全局变量污染
- 沙箱模式下自动处理 window 对象
- 注意第三方库的全局变量
3. 静态资源路径
- 子应用设置
publicPath为完整 URL - 使用 webpack 的
__webpack_public_path__
Cao Note