qiankun(https://qiankun.umijs.org/zh/guide/getting-started)基座也可以用多种脚手架搭建(此处以vue2.x 为例):
vue-cli搭建vue2.x 项目,然后安装qiankun:
$ yarn add qiankun # or npm i qiankun -S
基座配置:
我们将子应用的配置都放在:main/src/micro-app.js
下:
const microApps = [
{
name: 'sub-vue1',
entry: '//localhost:7777/',
activeRule: '/sub-vue1',
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-vue1' // 下发路由给子应用,子应用根据该值去定义qiankun环境下的路由
}
},
{
name: 'sub-vue2',
entry: '//localhost:7788/',
activeRule: '/sub-vue2',
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-vue2'
}
}
]
export default microApps
然后在src/main.js
中引入,qiankun这个库只需要在基座引入,在main.js
中注册子应用,为了方便管理
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import {
registerMicroApps,
addGlobalUncaughtErrorHandler,
start
} from 'qiankun'
import microApps from './micro-app'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import Element from 'element-ui'
Vue.config.productionTip = false
Vue.use(Element, {
size: 'medium' // set element-ui default size
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#main-app')
/**
* 注册微应用
* 第一个参数 - 微应用的注册信息
* 第二个参数 - 全局生命周期钩子
*/
registerMicroApps(microApps, {
// qiankun 生命周期钩子 - 微应用加载前
beforeLoad: app => {
NProgress.start()
console.log('before load app.name====>>>>>', app.name)
},
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
}
],
afterMount: [
app => {
// 加载微应用前,进度条加载完成
NProgress.done()
console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
}
]
})
/**
* 添加全局的未捕获异常处理器
*/
addGlobalUncaughtErrorHandler(event => {
console.error(event)
const { message: msg } = event
// 加载失败时提示
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
this.$message({
showClose: true,
message: '微应用加载失败,请检查应用是否可运行',
type: 'error'
})
}
})
// 导出 qiankun 的启动函数
start()
在App.vue
中,需要声明micro-app.js
配置的子应用挂载div(注意id一定要一致),以及基座布局相关的,大概这样:
用Vue-cli新建一个sub-vue1
的子应用(这里演示完全独立运行的3个项目),子应用的名称最好与父应用在src/micro-app.js
中配置的名称一致(这样可以直接使用package.json
中的name
作为output)。
vue.config.js
,devServer的端口改为与主应用配置的一致,且加上跨域headers
和output
配置。
// package.json的name需注意与主应用一致
const { name } = require('./package.json')
module.exports = {
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
}
},
devServer: {
port: process.env.VUE_APP_PORT, // 根目錄新建.env中VUE_APP_PORT=7777,与父应用的配置一致
headers: {
'Access-Control-Allow-Origin': '*' // 主应用获取子应用时跨域响应头
}
}
}
2、新增src/public-path.js
;(function () {
if (window.__POWERED_BY_QIANKUN__) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line
__webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}${process.env.BASE_URL}`
return
}
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
})()
3、src/router/index.js改为只暴露routes,new Router改到main.js中声明。
4、改造main.js,引入上面的public-path.js,改写render,添加生命周期函数等,最终如下:
import './public-path' // 注意需要引入public-path
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import VueRouter from 'vue-router'
import store from './store'
// import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import Element from 'element-ui'
Vue.config.productionTip = false
Vue.use(VueRouter)
Vue.use(Element, {
size: 'medium' // set element-ui default size
})
let instance = null
/**
* 渲染函数
* 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
*/
function render (props = {}) {
const { container, routerBase } = props
// 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
const router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
mode: 'history',
routes
})
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap () {
console.log('[vue] vue app bootstraped')
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount (props) {
console.log('[vue] props from main framework', props)
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount () {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
至此,基础版本的vue子应用配置好了,同理再运行一个vue项目(sub-vue2)即可查看效果(暂时这里不展示其他技术栈的应用)
注意:以上的基座(主应用)和微应用路由都只能采用 history模式
如果微应用采用的hash路由模式,则主应用需修改子应用配置
const microApps = [
{
name: 'sub-vue1',
entry: '//localhost:7777/',
//activeRule: '/sub-vue1',
activeRule: getActiveRule(`#/sub-vue1`),//修改子应用路由匹配规则
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-vue1' // 下发路由给子应用,子应用根据该值去定义qiankun环境下的路由
}
},
{
name: 'sub-vue2',
entry: '//localhost:7788/',
//activeRule: '/sub-vue2',
activeRule: getActiveRule(`#/sub-vue2`),//修改子应用路由匹配规则
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: '/sub-vue2'
}
}
]
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
export default microApps