微前端-乾坤技术汇总

196 阅读5分钟

微前端-乾坤技术汇总

一、什么是微前端?

微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。有一个基座应用(主应用),来管理各个子应用的加载和卸载。

所以微前端不是指具体的库,不是指具体的框架,不是指具体的工具,而是一种理想与架构模式。

微前端的核心三大原则就是:独立运行、独立部署、独立开发

二、技术应用实现。

选型:乾坤,无界,micro-app;

参考了网上很多资料技术,社区比较成熟的就只有乾坤。但是无界,micro-app这两个在技术实现上比乾坤性能更加优秀,采用的隔离应该也是最好的。后续大家可以都了解一下,我也会试着写一下其他两个的dom

三、乾坤实现

乾坤api:qiankun.umijs.org/zh/api#regi…

主应用:又称基座应用。我们可以将通用的登录,通用插件,通用状态,变量放在主应用。

配置:main.js

import Vue from 'vue'
import App from './App.vue'
import creatRouter from './router/routers';
import store from './store'
import SvgIcon from '@/components/SvgIcon'// svg组件
import { getToken } from '@/utils/auth'
import { buildMenus } from '@/api/system/menu'
import { filterAsyncRouter } from './store/modules/permission'

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

const whiteList = ['/login']// no redirect whitelist

import { registerMicroApps } from 'qiankun'; // qiankun.umijs.org/zh/api#regi…
//乾坤主要配置,五大系统基本配置
const apps = [
{
name: 'SCRM', // 应用的名字
entry: '//localhost:8014', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container: '#ScrmAppMain', // 容器名(此项目页面中定义的容器id,用于把对应的子应用放到此容器中)
activeRule: '/scrmApp/dashboard', // 激活的路径
},
{
name: '品宣', // 应用的名字
entry: '//localhost:8015', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container: '#BDMain', // 容器名(此项目页面中定义的容器id,用于把对应的子应用放到此容器中)
activeRule: '/bdApp/dashboard', // 激活的路径
},
{
name: 'studyPlatformApp', // 应用的名字
entry: '//localhost:8016', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container: '#studyPlatformMain', // 容器名(此项目页面中定义的容器id,用于把对应的子应用放到此容器中)
activeRule: '/studyPlatform/dashboard', // 激活的路径
// props: { a: 1 } // 传递的值(可选)
},
{
name: '数字化中心', // 应用的名字
entry: '//localhost:8017', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
container: '#dataManageMain', // 容器名(此项目页面中定义的容器id,用于把对应的子应用放到此容器中)
activeRule: '/dataManage/dashboard', // 激活的路径
// props: { a: 1 } // 传递的值(可选)
},
]

//注册应用。
registerMicroApps(apps, {
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: [(app) => console.log('before mount', app.name)],
beforeUnmount: ((app) => {
console.log("beforeUnmount:", app, "111111111111111111111111")
})
},);

Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.component('svg-icon', SvgIcon)
// Vue.use(router)

let router = creatRouter();
router.beforeEach((to, from, next) => {
if (getToken()) {
// 已登录且要跳转的页面是登录页
if (to.path === '/login') {
next({ path: '/' })
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => { // 拉取user_info
// 动态路由,拉取菜单
loadMenus(next, to)
// next({ ...to, replace: true })
}).catch((err) => {
console.log(err)
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
})
// 登录时未拉取 菜单,在此处拉取
} else if (store.getters.loadMenus) {
// 修改成false,防止死循环
store.dispatch('updateLoadMenus').then(() => { })
loadMenus(next, to)
// next({ ...to, replace: true })
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next(/login?redirect=${to.path}) // 否则全部重定向到登录页
}
}
})

export const loadMenus = (next, to) => {
buildMenus().then(res => {
const asyncRouter = filterAsyncRouter(res)
asyncRouter.push({ path: '*', redirect: '/404', hidden: true })
next({ ...to, replace: true })
})
}

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

这个文件跟例子里面的不同,是因为我们的项目是有权限的,所以不能直接在这里启动。

子应用:主要更改main.js,vue.config.js,router,public-path.js

main主要导出乾坤钩子。vue.config.js主要解决打包构建出来的文件格式,以及公共资源路径。router主要解决特定路由前缀,保证路由唯一。

public-path.js

if (window.POWERED_BY_QIANKUN) {
webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
}

main.js

import "./public-path";//这个文件一定要引入
import Vue from 'vue'
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router,
store,
provide: container ? container.mainAppRouter : {}, // 初始化时直接挂载到实例
render: (h) => h(App),
}).$mount(container ? container.querySelector("#GoodLongApp") : "#GoodLongApp"); // 这里是挂载到自己的html中 基座会拿到这个挂载后的html 将其插入进去
}
// if (window.POWERED_BY_QIANKUN) { // 动态添加publicPath
// webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
// }
if (!window.POWERED_BY_QIANKUN) {
// 默认独立运行
render();
}

// 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
// 子组件的协议就ok了
export async function bootstrap(props) {
// router.options = { ...router.options, ...props.mainAppRouter.router.options };
// store.commit('windowState',true)
store.state.publiced.windowState = true;
console.log(store.state)
}

export async function mount(props) {
console.log("mount-goodLong", props);
render(props);
}

export async function unmount() {
instance.destroy();instance.destroy(); instance.el.innerHTML = "";
instance = null;
router = null;
store.state.windowState = false;
}

export async function update(props) {
console.log("update-goodLong", props);
}

vue.config.js (webpack4)

'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')

function resolve(dir) {
return path.join(__dirname, dir)
}

const name = defaultSettings.title // 网址标题
const port = 8013 // 端口配置

// All configuration item explanations can be find in cli.vuejs.org/config/
module.exports = {
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: port,
headers: {
// 因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src'),
'@crud': resolve('src/components/Crud')
}
},
//这个地方很重要。
output: {
library: 'goodLong',
libraryTarget: 'umd',
jsonpFunction: 'webpackJsonp_goodLong', //webpack5这个没用了
globalObject: 'window'
}
},
chainWebpack(config) {}
}

升级到webpack5 会出现一个让人猝不及防的问题,子应用路由无法使用了。这都是webpack5 tree shaking 导致的。

问题解决只需要在package.json文件增加一个属性

"sideEffects":['src/public-path']

虽然一个简短的配置就能就决问题但是牵扯的东西却不少。

1.熟悉微前端qiankun的原理

2.熟悉webpack的原理以及webpack动态懒加载实现的原理

3.熟悉webpack的__webpack_pulicPath__属性的意义

4.知道tree sharking

5.了解webpack5的tree sharking配置

这里引用一下某位大佬的文章:juejin.cn/post/702133…

四、注意事项:

  • webpack 的 publicPath 值只能在入口文件修改,之所以单独写到一个文件并在入口文件最开始引入,是因为这样做可以让下面所有的代码都能使用这个。
  • 路由文件需要 export 路由数据,而不是实例化的路由对象,路由的钩子函数也需要移到入口文件。
  • 在 mount 生命周期,可以拿到父项目传递过来的数据,router 用于跳转到主项目/其他子项目的路由,store 是父项目的实例化的 Vuex(也可以传递其他数据过来)。