微前端说明以及使用
1、微前端概论
1. 微前端概念
- 类型<iframe></iframe>一样,只不过微前端是用fetch去请求js并渲染在指定的DOM容器。
· 跟技术栈无关,任何前端技术栈都可以接入。
· 多个应用结合在一起,可以一起运行,又可以单独运行。
· 一个复杂庞大的项目拆成多个微应用,单独开发、单独部署、单独测试,互不影响。
· 原理是通过在主应用引入每个子应用的入口文件(main.js),进行解析,并指定渲染的容器
2. 什么时候需要用到微前端
- 庞大的系统需要拆分给不同团队去做时。
系统里面有很多个模块,模块里面又很多个子模块时。
2、微前端使用说明
qiankun(乾坤) 就是一款由蚂蚁金服推出的比较成熟的微前端框架,基于 single-spa 进行二次开发,用于将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
基于 qiankun+vue2.0 技术栈实现的前端微应用架构,实现了动态路由主子应用以及子子应用之间的通信,并做了简单的自动化脚本命令
1、微前端的相关文档
Qiankun: https://qiankun.umijs.org/zh/guide
2、关于项目依赖包 common
包内容简介:
所有子应用都需要对主应用下发的数据进行接收及处理、如果数据修改则通知到其他应用以及对主应用下发的路由数据进行处理,因为这些逻辑完全一样,因此将这些实现逻辑提取为一个 npm 包统一管理。
实现的功能:
在 vuex 中动态添加了 global 模块及 routes 模块;
- global 模块:封装了全局下发的数据,以及数据修改通知到其他应用;
- routes 模块:路由数据的封装以及组件的导入。
3、主项目中微前端的相关文件说明
3、微前端子应用代码改造
a.修改package.json:
- name属性为应用名。
- 设置header允许跨域请求。
b.修改vue.config.js的publicPath属性应用名。
const packageName = require("./package.json").name; const Timestamp = new Date().getTime(); module.exports = { assetsDir: "./", publicPath: process.env.NODE_ENV === "production" ? "/sub/" : "/", outputDir: "sub", configureWebpack: { output: { library: `${packageName}-[name]`, libraryTarget: "umd", jsonpFunction: `webpackJsonp_${packageName}`, filename: "js/[name]." + Timestamp + ".js", chunkFilename: "js/[name]." + Timestamp + ".js", }, }, devServer: { port: 8012, // 在.env中VUE_APP_PORT=7788,与父应用的配置一致 headers: { "Access-Control-Allow-Origin": "*", // 主应用获取子应用时跨域响应头 }, }, };
设置唯一端口,在.env里面设置端口号,这里端口号没有说必须要这里设置,你也在其他地方设置,看你项目设计而定,但是端口号必须唯一,不跟已有应用发生冲突
c.在src下新建一个public-path.js文件
(function () { if (window.__POWERED_BY_QIANKUN__) { if (process.env.NODE_ENV === "development") { // eslint-disable-next-line no-undef __webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}/`; return; } // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } })();
d.Src下新建common文件夹,新建index.js文件(封装用于接收主应用参数)
import Vue from 'vue' import validator from 'validator' Vue.prototype.$validator = validator function initGlobalState(store, props = {}) { registerGlobalModule(store, props) } function registerGlobalModule(store, props = {}) { // 是否传入store及所传入的store是否为一个vuex的实例 if (!store || !store.hasModule) { return } // 获取初始化的state const initState = (props.getGlobalState && props.getGlobalState()) || { userInfo: {}, globalConfig: {} } // 将父应用的数据存储到子应用中,命名空间固定为global if (!store.hasModule('global')) { const globalModule = { namespaced: true, state: initState, actions: { // 子应用改变state并通知父应用 setGlobalState({ commit }, payload) { commit('setGlobalState', payload) commit('emitGlobalState', payload) }, // 初始化,只用于mount时同步父应用的数据 initGlobalState({ commit }, payload) { commit('setGlobalState', payload) } }, mutations: { setGlobalState(state, payload) { // eslint-disable-next-line state = Object.assign(state, payload); }, // 通知父应用 emitGlobalState(state) { if (props.setGlobalState) { props.setGlobalState(state) } } } } store.registerModule('global', globalModule) } else { // 每次mount时,都同步一次父应用数据 store.dispatch('global/initGlobalState', initState) } } export default { initGlobalState }
e.main.js改造
/* * @Author: your name * @Date: 2021-09-05 15:36:58 * @LastEditTime: 2021-10-28 10:16:46 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: \sub_notice\src\main.js */ import './public-path' import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import common from './common/index' import ElementUI from 'element-ui' import iView from 'iview' import 'iview/dist/styles/iview.css'; import Utils from '@/utils/util' // 新字体图标 有变动替换下方 import '@/assets/fontsNew/iconfont.css' import 'element-ui/lib/theme-chalk/index.css' import '@/utils/rem' Vue.config.productionTip = false let instance = null Vue.use(ElementUI) Vue.use(iView) Vue.prototype.$utils = Utils function render(props = {}) { const { container } = props instance = new Vue({ router, store, render: (h) => h(App) }).$mount(container ? container.querySelector('#app') : '#app') } console.log(router) if (!window.__POWERED_BY_QIANKUN__) { render(); } export async function bootstrap(props) { console.log('[vue] vue app bootstraped', props) } export async function mount(props) { console.log('[vue] props from main framework', props) common.initGlobalState(store, props) render(props) } export async function unmount() { instance.$destroy() instance.$el.innerHTML = '' instance = null }
f.路由拦截
路由拦截设计,当一起运行时,则交给主应用处理;当独立运行时,则由运行的子应用处理,判断是一起运行还是独立运行可以通过window.POWERED_BY_QIANKUN__的值判断。
路由模式采用hash,路由跳转采用this.$router.push
g.获取主应用参数
$store.state
h.修改公共参数
this.setGlobalState({ userInfo: { name: "xxxxxx" } });
5、微前端子应用接入示例
1、子应用的准备
先确保子应用能正常运行并且能访问对应的菜单路由。
2、配置菜单
此系统中因无菜单管理,相关菜单需要在数据库中配置。
a、新增菜单
b、配置角色与菜单对应关系
c、主应用中新增子应用菜单路由
d、主应用访问子应该菜单(成功显示)
3、主应用概述
技术栈:vue+vue-router+vuex+ivew/vant+axios 安装 npm install 本地启动 1、npm run serve 2、打开http://localhost:8081/travel_guide/travel/(默认首页) 环境变量与构建 环境变量:dev、test、prod 测试环境构建: run buildtest 生产环境构建: run buildprod PC组件库iview 文档:https://www.iviewui.com 引入方式:src/plugins, 注册引用,按需引入 路由配置 配置: src/router/routes 文档:https://router.vuejs.org/zh/ 数据存储于请求 vuex+axios 文档 vuex https://vuex.vuejs.org/zh/guide/ axios https://www.kancloud.cn/yunye/axios/234845 配置 关于路由守卫,仿vue-admin
4、子应用概述
技术栈:vue+vue-router+vuex+ivew/vant+axios 安装 npm install 本地启动 1、npm run serve 2、打开 http://localhost:8012/(默认首页) 环境变量与构建 环境变量:dev、test、prod 测试环境构建: run buildtest 生产环境构建: run buildprod PC 组件库 iview 文档:https://www.iviewui.com 引入方式:src/plugins, 注册引用,按需引入 移动端组件库 vant 文档:https://www.iviewui.com 路由配置 配置: src/router/routes 文档:https://router.vuejs.org/zh/ 首页:http://localhost:8012/ 数据存储于请求 vuex+axios 文档 vuex https://vuex.vuejs.org/zh/guide/ axios https://www.kancloud.cn/yunye/axios/234845