1.为什么要有微前端?
随着前端技术的逐步发展,前端业务涉及越来越广,前端应用越来越大,所有功能集合在一起。微前端就是分了拆分一个个业务模块的代码作为独立的微应用。
2.微前端的好处
1.与技术栈无关,可以接入任何技术栈的子应用
2.基于 single-spa 封装,提供了更加开箱即用的 API。
3.微应用之间可以独立开发,独立部署。
3.Demo实践
1.主应用开发:
1.安装qiankun yarn add qiankun # 或者 npm i qiankun -S
2.在主应用中注册子应用
import { registerMicroApps, start } from 'qiankun';
//注册子应用(main.js)
registerMicroApps([
{
name: 'vue app', // app name registered
entry: '//localhost:8082',
container: '#vueApp',
activeRule: '/vue',
},
{
name: 'vue2 app', // app name registered
entry: '//localhost:8083',
container: '#child',
activeRule: '/vue2',
},
]);
start();
3.写入一个用于挂载微应用的容器(App.vue)
<el-menu :router="true" mode="horizontal" :default-active="activeIndex">
<!--基座中可以放自己的路由-->
<el-menu-item index="/">Home</el-menu-item>
<!--引用其他子应用-->
<el-menu-item index="/vue2">子应用2</el-menu-item>
<el-menu-item index="/vue">vue应用</el-menu-item>
</el-menu>
<div id="vueApp"></div> <!--注意和container一致-->
<div id="child"></div>
2.子应用开发
1.main.js
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue2' : '/', // path 需要和基座(base)应用保持一致
routes: routes,
store: store,
mode: 'history'
});
instance = new Vue({
router,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 解决基础路径不正确的问题
if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//导出生命周期
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('vue2 app 启动');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log('卸载')
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
console.log('update props', props);
}
2.注意在本地测试时要让微应用允许跨域访问(vue.config.js)
devServer: {
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
Demo地址: github.com/s980711272/…
4.缓存问题解决
function render(){
// 这里必须要new一个新的路由实例,否则无法响应URL的变化。
router = new Router({
mode: 'history',
base: !window.__POWERED_BY_QIANKUN__ ? '' : 'bms',
routes: constantRoutes,
});
router.addRoutes(asyncRoutes);
//缓存实例化
if (window.__POWERED_BY_QIANKUN__ && window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__) {
const cachedInstance = window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__;
// 从最初的Vue实例上获得_vnode
const cachedNode =
(cachedInstance.cachedInstance && cachedInstance.cachedInstance._vnode) ||
cachedInstance._vnode;
// 让当前路由在最初的Vue实例上可用
router.apps.push(...cachedInstance.catchRoute.apps);
instance = new Vue({
router,
store,
render: () => cachedNode
});
// 缓存最初的Vue实例
instance.cachedInstance = cachedInstance;
router.onReady(() => {
const { path } = router.currentRoute;
const { path: oldPath } = cachedInstance.$router.currentRoute;
// 当前路由和上一次卸载时不一致,则切换至新路由
if (path !== oldPath) {
cachedInstance.$router.push(path);
}
});
instance.$mount('#micro-bms');
} else {
//console.log('正常实例化');
// 正常实例化
instance = new Vue({
el: '#micro-bms',
router,
store,
render: h => h(App)
});
}
}
export async function mount(props) {
Vue.prototype.$poweredByQiankun = true;
render()
}
export async function unmount() {
//console.log('micro-bms 卸载');
//缓存
const cachedInstance = instance.cachedInstance || instance;
window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__ = cachedInstance;
const cachedNode = cachedInstance._vnode;
if (!cachedNode.data.keepAlive) cachedNode.data.keepAlive = true;
cachedInstance.catchRoute = {
apps: [...instance.$router.apps]
}
instance.$destroy();
router = null;
instance.$router.apps = [];
}