概论
主要是有一个微前端的主权项目,实现对微服务的调用,类似iframe显示,父应用和子应用可以通过一些数据通信方式实现数据通信。
代码
- 微前端注册
import Vue from 'vue'
import App from './App.vue'
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
import microApps from './micro-app'
import 'nprogress/nprogress.css'
Vue.config.productionTip = false
const instance = new Vue({
render: h => h(App)
}).$mount('#app')
// 定义loader方法,loading改变时,将变量赋值给App.vue的data中的isLoading
function loader (loading) {
if (instance && instance.$children) {
// instance.$children[0] 是App.vue,此时直接改动App.vue的isLoading
instance.$children[0].isLoading = loading
}
}
// 给子应用配置加上loader方法
const apps = microApps.map(item => {
return {
...item,
loader
}
})
registerMicroApps(apps, {
beforeLoad: app => {
console.log('before load app.name====>>>>>', app.name)
},
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
}
],
afterMount: [
app => {
console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
}
]
})
setDefaultMountApp('/sub-vue')
start()
- 微前端微应用数组
import store from './store'
const microApps = [
{
name: 'sub-vue',
entry: process.env.VUE_APP_SUB_VUE,
activeRule: '/sub-vue'
},
{
name: 'sub-react',
entry: process.env.VUE_APP_SUB_REACT,
activeRule: '/sub-react'
},
{
name: 'sub-html',
entry: process.env.VUE_APP_SUB_HTML,
activeRule: '/sub-html'
}
]
const apps = microApps.map(item => {
return {
...item,
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: item.activeRule, // 下发基础路由
getGlobalState: store.getGlobalState // 下发getGlobalState方法
}
}
})
export default apps
数据通信的主要方式就是全局state了
props: {
routerBase: item.activeRule, // 下发基础路由
getGlobalState: store.getGlobalState // 下发getGlobalState方法
}
- 子应用 sub-vue 代码
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import { store as commonStore } from 'common'
import store from './store'
import VueRouter from 'vue-router'
Vue.config.productionTip = false
let instance = null
function render (props = {}) {
const { container, routerBase } = props
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__) {
// 这里是子应用独立运行的环境,实现子应用的登录逻辑
// 独立运行时,也注册一个名为global的store module
commonStore.globalRegister(store)
// 模拟登录后,存储用户信息到global module
const userInfo = { name: '我是独立运行时名字叫张三' } // 假设登录后取到的用户信息
store.commit('global/setGlobalState', { user: userInfo })
render()
}
export async function bootstrap () {
console.log('[vue] vue app bootstraped')
}
export async function mount (props) {
console.log('[vue] props from main framework', props)
commonStore.globalRegister(store, props)
render(props)
}
export async function unmount () {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
有一个全局的状态判断是否是微前端中运行
window.__POWERED_BY_QIANKUN__
render 方法中可以获取到全局的数据,然后写入本子应用中
function render (props = {}) {
const { container, routerBase } = props
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')
}
以下代码主要是把全局store注册到子应用关联到子应用的store中,方便调用
if (!window.__POWERED_BY_QIANKUN__) {
// 这里是子应用独立运行的环境,实现子应用的登录逻辑
// 独立运行时,也注册一个名为global的store module
commonStore.globalRegister(store)
// 模拟登录后,存储用户信息到global module
const userInfo = { name: '我是独立运行时名字叫张三' } // 假设登录后取到的用户信息
store.commit('global/setGlobalState', { user: userInfo })
render()
}
html子应用的应用方式
入口html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sub-html子应用开发环境</title>
<style>
.container{
padding: 20px;
}
</style>
</head>
<body>
<div class="container">
纯原生HTML子应用, 当前处于 <code id="current-env">独立运行</code> 环境。
</div>
<!-- 开发环境的相对路径引用方式 -->
<script src="./js/main.js"></script>
<!-- 由于没有webpack自动构建,线上path需要根据部署路径手动更换,本项目把html文件分开管理,线上环境的html放在 public 文件夹 -->
<!-- <script src="/subapp/sub-html/js/main.js"></script> -->
</body>
</html>
main.js
const render = (options) => {
// options是基座下发的参数,可以保存到全局的状态管理或其他地方,用于后面与基座进行通信
// 可通过 options.getGlobalState() 获取基座下发的数据
// options.setGlobalState({user: {name: ''}}) 改变全局的数据
// options.onGlobalStateChange 监听全局数据的变化
document.querySelector('#current-env').innerHTML = 'qiankun'
const globalState = options.getGlobalState()
// 展示基座下发的状态
const node = document.createElement('div')
node.innerHTML = `基座下发的globalState: <code>${JSON.stringify(globalState)}</code>。<a target="_blank" href="${window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__}">打开独立运行环境</a>`
document.querySelector('.container').appendChild(node)
return Promise.resolve();
};
(global => {
global['prehtml'] = {
bootstrap: () => {
console.log('purehtml bootstrap');
return Promise.resolve();
},
mount: (options) => {
console.log('purehtml mount', options);
return render(options);
},
unmount: () => {
console.log('purehtml unmount');
return Promise.resolve();
},
};
})(window);
window.prehtml.mount 应该是个全局的方法,微前端中会自动被触发,然后就会运行到render函数了
微前端原理
js沙箱环境
总的来说就是搞一个虚拟的window全局对象,通过proxy代理这个虚拟window全局对象,来代理操作window真实对象,单子应用切换的时候,这些代理到window的操作都会清除重置
参考文章
前端工程师、程序员