qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
// @vue/cli 5.0.4
vue create main-app
yarn add qiankun
新增文件夹micros
,文件夹下新增 app.js
// src/micros/app.js
// 微应用信息
const apps = [
{
name: 'vue-app', // 微应用的名称,微应用之间必须确保唯一
entry: '//localhost:8081/', // 微应用的入口,运行地址
container: '#micro-container', // 微应用的容器节点
activeRule: '/vue-app' // 微应用的激活规则
}, {
name: 'react-app'
entry: '//localhost:8082/',
container: '#micro-container',
activeRule: '/react-app'
}
]
export default apps
micros
下新增 index.js
// src/micros/index.js
import { addGlobalUncaughtErrorHandler, registerMicroApps, start } from 'qiankun'
// 微应用的信息
import apps from './app'
/**
* registerMicroApps(apps, lifeCycles?)
* apps: 必选,微应用的一些注册信息
* lifeCycles: 可选,全局的微应用生命周期钩子
*/
registerMicroApps(apps, {
// 加载前
beforeLoad: (app) => console.log('before load', app.name),
// 挂载前
beforeMount: (app) => console.log('before mount', app.name),
// 挂载后
afterMount: (app) => console.log('after mount', app.name),
// 卸载前
beforeUnmount: (app) => console.log('before unmount', app.name),
// 卸载后
afterMount: (app) => console.log('after unmount', app.name)
})
// 添加全局的未捕获异常处理器
addGlobalUncaughtErrorHandler((event) => {
console.error(event)
const { message } = event
if (message && message .includes('died in status LOADING_SOURCE_CODE')) {
console.error('微应用加载失败')
}
})
export default start
main.js
中启动
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import start from './micros'
start()
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
<template>
<div id="app">
<nav class="menu">
<router-link to="/">Homerouter-link>
<router-link to="/vue-app">vue-approuter-link>
<router-link to="/react-app">react-approuter-link>
nav>
<div class="main">
<router-view>router-view>
<div id="micro-container">div>
div>
div>
template>
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ '@/views/home.vue')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
vue create vue-app
src
下新增 public-path.js
// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ '../views/home.vue')
}
]
const router = new VueRouter({
mode: 'history',
// base设置,与主应用activeRule规则一致
base: window.__POWERED_BY_QIANKUN__ ? '/vue-app' : '/',
routes
})
export default router
main.js
文件修改// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './public-path'
Vue.config.productionTip = false
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()
}
// 微应用生命周期
// bootstrap 初始化
export async function bootstrap () {
console.log('vue-app bootstraped')
}
// mount 挂载
export async function mount (props) {
console.log('vue-app mount', props)
render(props)
}
// unmount 卸载
export async function unmount () {
console.log('vue-app unmount')
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
vue.config.js
文件修改// 这里的 name 应为 'vue-app' 与注册名一致
const { name } = require('./package')
module.exports = {
devServer: {
port: 8081,
historyApiFallback: true,
allowedHosts: 'all',
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: name,
// // 把微应用打包成 umd 库格式
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${name}`
}
}
}
// create-react-app 5.0.1
create-react-app react-app
src
下新增public-path.js
// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
App.js
修改// src/App.js
import { Routes, Route, BrowserRouter as Router} from 'react-router-dom'
import './App.css'
import Home from './components/Home'
function App() {
return (
<div className="App">
{/* base设置,与主应用activeRule规则一致 */}
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/react-app' : '/'}>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</Router>
</div>
)
}
export default App
index.js
修改// src/index.js
import React from 'react'
import { createRoot } from 'react-dom/client' // react18
import App from './App'
import reportWebVitals from './reportWebVitals'
import './public-path'
let instance = null
function render(props) {
const { container } = props
const root = createRoot(container ? container.querySelector('#root') : document.querySelector('#root'))
root.render(<App />)
instance = root
}
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
export async function bootstrap() {
console.log('react-app bootstraped')
}
export async function mount(props) {
console.log('react-app mount', props)
render(props)
}
export async function unmount(props) {
console.log('react-app unmount')
instance.unmount()
instance = null
}
reportWebVitals()
webpack
配置安装插件 react-app-rewired
,当然也可以选择其他的插件,例如 @rescripts/cli
yarn add react-app-rewired -D
根目录新增config-overrides.js
const path = require("path")
const resolve = (dir) => path.join(__dirname, dir)
// 这里的 name 应为 'react-app' 与注册名一致
const { name } = require("./package")
module.exports = {
webpack: (config) => {
config.output.library = name
config.output.libraryTarget = 'umd'
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
config.output.globalObject = 'window'
config.resolve.alias = {
...config.resolve.alias,
'@': resolve('src')
}
return config
},
devServer: function (configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost)
config.allowedHosts = 'all'
config.headers = {
'Access-Control-Allow-Origin': '*',
}
config.historyApiFallback = true
return config
}
}
}
修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
}