微前端
微前端特点
- 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
- 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
- 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
- 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
- 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率
微前端方案
- 基于 iframe 完全隔离的方案
- 基于 single-spa 路由劫持方案
- 阿里 qiankun
iframe
优点
- 非常简单,无需任何改造
- 完美隔离,JS、CSS 都是独立的运行环境
- 不限制使用,页面上可以放多个 iframe 来组合业务
缺点
- 无法保持路由状态,刷新后路由状态就丢失
- 完全的隔离导致与子应用的交互变得极其困难,只能采用postMessage方式
- iframe 中的弹窗无法突破其本身
- 整个应用全量资源加载,加载太慢
single-spa
qiankun
特点
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
快速使用
主应用配置
$ yarn add qiankun # 或者 npm i qiankun -S
main.js 主应用中注册子应用
import {
registerMicroApps,
start,
setDefaultMountApp
} from 'qiankun'
// 在主应用中注册子应用
registerMicroApps([{
name: "vue app",
entry: "//localhost:8090", // 子应用链接
container: '#vue', // 子应用容器
activeRule: '/vue', // 跳转子应用路由
props: {
appContent: '我是子应用传给主应用的值'
}
}
])
setDefaultMountApp("/vue")
// 启动
start()
router/index.js 主项目是 vue3 + vue-router@4配置
//主应用使用的嵌套路由
router.beforeEach((to, from, next) => {
if (!window.history.state.current) window.history.state.current = to.fullPath;
if (!window.history.state.back) window.history.state.back = from.fullPath;
// 手动修改history的state
return next();
});
子应用容器
<div id="vue"></div>
webpack配置 vue.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server:{
port:9000,
headers: { // 允许子应用跨域
'Access-Control-Allow-Origin': '*',
}
}
})
vue2.0 子应用配置
src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import './public-path'
Vue.config.productionTip = false;
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app-vue") : "#app-vue");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 导出相应的生命周期钩子
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("mount");
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log("unmount")
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
}
路由配置 router/index.js
const router = new VueRouter({
// 这里和主应用中注册子应用时的activeRule对应
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/", // base路由和注册子应用入口保持一致
mode: "history",
routes,
});
webpack配置 vue.config.js
const { defineConfig } = require('@vue/cli-service')
const { name } = require("./package");
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8090, //子应用启动端口号,不可随意修改,与上文中父应用注册的子应用端口号对应
headers: {
"Access-Control-Allow-Origin": "*", //循序跨域
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
})
项目中index.html 和App.vue中的id 都改为 app-vue
vue3.0子应用配置
main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createWebHistory } from 'vue-router'
import createRouter from "./route"
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let router = null
let instance = null
let history = null
function render(props) {
console.log("__POWERED_BY_QIANKUN__", qiankunWindow.__POWERED_BY_QIANKUN__)
const container = props ? props.container : undefined
history = createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/two' : '/')
router = createRouter(history)
instance = createApp(App)
instance.use(router)
instance.mount(container ? container.querySelector('#app-vue3') : '#app-vue3')
}
renderWithQiankun({
mount(props) {
render(props)
instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange
instance.config.globalProperties.$setGlobalState = props.setGlobalState
},
bootstrap() {
console.log('%c%s', 'color: green;', 'vue3.0 app bootstraped')
},
unmount() {
instance.unmount()
instance._container.innerHTML = ''
instance = null
router = null
history.destroy()
},
update() {
console.log('update')
}
})
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render(undefined)
}
route.js
import { createRouter } from 'vue-router'
const routes = [
{
path:'/',
name:'Home',
component:()=>import('../view/Home.vue')
}
]
export default function (history) {
return createRouter({
history: history,
routes,
})
}
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from "vite-plugin-qiankun"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
qiankun("VUE3.0", {
useDevMode: true
})
],
server:{
port:5000
}
})
npm install vite-plugin-qiankun
源码
主应用: https://gitee.com/wy-yyb/vue3-main.git
vue2子应用:https://gitee.com/wy-yyb/vue2.0.git
vue3子应用https://gitee.com/wy-yyb/vue3.0.git