从零开始搭建一个qiankun+vue微前端应用

3,656 阅读3分钟

qiankun 微前端

为啥不用iframe

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于 他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

iframe的弊端

  1. url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  2. UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中
  3. 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

aabb0d8d021bf312dfa8a000950e5167.png

废话不多说 上代码
在搭建qiankun微前端的时候要搭建一个主应用的一个基座

我是以vue为基座 可以嵌入其他技术栈的项目vue react jquery Angular。。。。

qiankun主应用(基座)

1. 我们以qiankun + vue2.0为例创建一个主应用和一个子应用,路由模式均为history模式
vue create qiankun-main
// 2. 在主应用qiankun-main
安装qiankun yarn add qiankun 或者 npm install qiankun --save、

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

import { registerMicroApps, start } from 'qiankun' //新增部分,导入qiankun中的两个方法
const apps = [{
    name: 'vueApp', //子应用的名称
    entry: 'http://172.18.61.27:8083', //子应用的域名
    container: '#vue', //承载子应用的容器,在上面App.vue中定义
    activeRule: '/vue', // 被激活的子应用的路由
}]
registerMicroApps(apps); //注册子应用
start(); //启动qiankun

new Vue({
    router,
    render: h => h(App)
}).$mount('#app');

src/router/index.js


import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from "../views/home/home.vue";
import About from "../views/about";

Vue.use(VueRouter)

const routes = [{
        path: '/',
        name: 'Home',
        // redirect: '/home',
        component: Home,
    },
    {
        path: '/about',
        name: 'About',
        component: About,
    },

]

const router = new VueRouter({
    routes,
    base: '',
    mode: 'history',
})

export default router

app.vue

<template>
	<div id="app">
		<div id="nav">
			<router-link to="/">Home</router-link> |
			<router-link to="/about">About</router-link> |
			<router-link to="/vue">VueApp</router-link> <!--新增部分-->
		</div>
		<router-view />
		<div id="vue"></div><!--新增部分,用于承载子应用-->
	</div>
</template>

vue.config.js

module.exports = {
    lintOnSave: false, // 关闭eslint检测
    devServer: {
        port: 8084, //这里的端口是必须和父应用配置的子应用端口一致
        headers: {
            //因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域
            'Access-Control-Allow-Origin': '*'
        },
        overlay: {
            warning: false,
            errors: false
        }
    },
    configureWebpack: {
        output: {
            //资源打包路径
            library: 'vueApp',
            libraryTarget: 'umd'
        }
    }
}

子应用(微服务)

vue create qiankun-child

min.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

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');
}
export async function mount(props) {
    render(props);
}

//判断当前运行环境是独立运行的还是在父应用里面进行运行,配置全局的公共资源路径
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//如果是独立运行window.__POWERED_BY_QIANKUN__=undefined
if (!window.__POWERED_BY_QIANKUN__) {
    render()
}
//最后暴露的三个方法是固定的,加载渲染以及销毁
export async function bootstrap() {}
// export async function mount(props){
//     render();
// } 
export async function unmount() {
    instance.$destroy();
}
export async function update(props) {
    //   console.log('update props', props);
}

src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Music from '../views/Home.vue'
import '../public-path'

Vue.use(VueRouter)

const routes = [{
        path: '/Music',
        name: 'Music',
        component: Music
    },
    {
        path: '/Dome',
        name: 'Dome',
     
        component: () =>
            import ( /* webpackChunkName: "about" */ '../views/About.vue')
    }
]

const router = new VueRouter({
    mode: 'history',
    base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
    routes
})

export default router

app.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/Music">Music</router-link> |
      <router-link to="/Dome">About</router-link> |
      <router-link to="/about">MVVM</router-link>
    </div>
    <router-view/>
  </div>
</template>

vue.cofig.js

module.exports = {
    lintOnSave: false,
    devServer: {
        port: 8083,
        headers: {
            "Access-Control-Allow-Origin": "*"
        }
    },
    configureWebpack: {
        output: {
            library: 'vueApp',
            libraryTarget: 'umd'
        }
    }
}

页面效果

子页面应用

捕获.PNG

主页页面

捕获22.PNG

嵌套页面

捕获11.PNG