在当今的软件开发领域,前端技术迅速发展,不断涌现出新的架构和工具。微前端是一种创新的前端架构,旨在解决传统单体化前端应用面临的一系列挑战。本文将深入探索微前端的概念、优势以及如何使用现有技术构建微前端架构。
一、微前端的概念 微前端是一种将前端应用拆分为更小、可独立部署和开发的模块的架构风格。它通过将整个前端应用拆分为较小的独立部分,每个部分都可以独立开发、部署和运行,从而实现了团队间的解耦合,提高了开发效率和可维护性。
二、微前端的优势
模块化开发:微前端架构将前端应用拆分为模块,每个模块有自己的生命周期和独立的开发流程。这样可以提高团队间的协作效率,同时也使得前端应用更加易于维护和修改。
技术栈无关性:微前端允许使用不同的技术栈开发不同的模块,例如React、Vue、Angular等。这使得团队可以在保持自己的首选技术栈的同时,整合各种技术栈的优势并共同构建一个完整的前端应用。
独立部署和运行:微前端使得每个模块都可以独立部署和运行,这意味着我们可以针对某个具体的功能或需求进行快速的迭代和发布,而不会影响整个应用的运行。
三、构建微前端架构的实践
选择合适的架构类型:目前,市面上有多种微前端架构的实现方式,如基于路由的微前端、基于组件的微前端等。根据团队的需求和实际情况选择合适的架构类型。
模块化拆分:将前端应用拆分为多个独立的模块,每个模块都有自己的代码库和构建过程。使用工具如Webpack或Rollup等进行打包和构建。
统一的样式和设计:为了保持一致的用户体验,使用共享的样式和设计规范,并将其封装为共享组件或npm包,供所有模块使用。
共享数据和状态管理:采用统一的数据状态管理工具(如Redux、MobX或Apollo)来管理模块间的共享数据和状态,确保模块间可以相互通信和数据共享。
异步加载和按需加载:使用异步加载和按需加载的技术,例如Webpack的Code Splitting或SystemJS的动态模块加载,以优化应用的加载速度和性能。
结语: 微前端架构是一种创新的前端架构模式,可以将复杂的前端应用分解为更易于维护和开发的模块。通过解耦合、模块化开发和独立部署的优势,微前端架构能够提高团队的效率和应用的可维护性。在构建微前端架构时,选择合适的架构类型,进行模块化拆分,并利用工具和技术来优化应用的性能和用户体验。微前端将是未来前端技术发展的重要趋势之一,我们应该积极探索和实践。
以下是项目应用具体配置
应用配置
一、先准备多个项目,区分主项目和子项目(我这边使用的vue2项目)
一、主应用配置
1.在主项目中进行依赖下载
// npm下载
npm i qiankun --save
// 或者使用yran
yarn add qiankun
2.在主项目中创建一个文件夹 src/qiankun qiankun文件下建两个文件index.js和app.js
// src/qiankun/app文件 (用于添加子应用的配置信息)
// 子应用列表
const apps = [
{
name: 'test', // 子应用app name 推荐与子应用的package的name一致
entry: '//localhost:6061/', // 子应用的入口地址,就是你子应用运行起来的地址
activeRule: '/children', // 子应用的路由前缀
container: '#qiankun-children', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】
props: { // 主应用向子应用传递的参数
time:new Date().getTime()
},
},
]
export default apps
// src/qiankun/index.js
import { addGlobalUncaughtErrorHandler, registerMicroApps, start} from 'qiankun'
// 微应用的信息
import apps from './app'
/**
* 注册微应用
* 第一个参数 - 微应用的注册信息
* 第二个参数 - 全局生命周期钩子
*/
registerMicroApps(apps, {
// qiankun 生命周期钩子 - 微应用加载前
beforeLoad: (app) => {
// 加载微应用前,加载进度条
console.log('before load=====', app.name)
return Promise.resolve()
},
// qiankun 生命周期钩子 - 微应用挂载后
afterMount: (app) => {
// 加载微应用前,进度条加载完成
console.log('after mount=====', app.name)
return Promise.resolve()
}
}
)
/**
* 添加全局的未捕获异常处理器
*/
addGlobalUncaughtErrorHandler((event) => {
console.error(event)
const { message: msg } = event
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
console.error('微应用加载失败,请检查应用是否可运行')
}
})
// 导出 qiankun 的启动函数
export default start
3.在main.js中引入并调用
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入qiankun文件
import start from '@/qiankun'
start()
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4.在路由中添加用于跳转子应用的路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
// 匹配子应用
path: '/children',
name: 'children',
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
5.建议固定端口 (主应用不固定也可以)
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 6060, // 端口号
},
})
二、子应用配置
1.在src文件下创建public-path.js文件
// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在这一步可能会报 error '__webpack_public_path__' is not defined 如果出现这个报错在package.json文件中添加以下代码
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {},
"globals": { //添加这个代码
"__webpack_public_path__": true
}
},
并在main.js中引入
import "./public-path";
2.暴露主应用生命周期钩子,修改渲染方法(因为vue都挂载在#app)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入public-path文件
import "./public-path";
Vue.config.productionTip = false
// 定义一个Vue实例
let instance = null
// 渲染方法
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: (h) => h(App)
}).$mount(container ? container.querySelector('#app'): '#app')
}
// 独立运行时
if(!window.__POWERED_BY_QIANKUN__) {
render()
}
//暴露主应用生命周期钩子
/**
* bootstrap : 在微应用初始化的时候调用一次,之后的生命周期里不再调用
*/
export async function bootstrap() {
console.log('vue2-app bootstraped');
}
/**
* mount : 在应用每次进入时调用
*/
export async function mount(props) {
// props是主应用传递的值
console.log('vue2-app mount', props);
render(props);
}
/**
* unmount :应用每次 切出/卸载 均会调用
*/
export async function unmount() {
console.log("vue2-app unmount")
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
3.固定子应用端口号配置跨域及打包配置
const { defineConfig } = require('@vue/cli-service')
const { name } = require("./package.json");
module.exports = defineConfig({
transpileDependencies: true,
// publicPath:`/children/${name}`,
devServer: {
port: 6061, //固定端口号,主应用要配置建议固定
// 配置跨域请求 因为乾坤是通过路由拦截方式实现微前端所以会产生跨域
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 打包配置
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // umd格式
chunkLoadingGlobal: `webpackJsonp_${name}`,
}
}
})
到这里如果子路由是hash模式已经可以了,如果是history模式加上以下配置
// src/router/index.js
const router = new VueRouter({
mode: 'hash',
// 在这里进行判断
base:window.__POWERED_BY_QIANKUN__ ?'/children': process.env.BASE_URL,
routes
})
以下是效果图
应用通讯
一、参数传递
针对不同场景我可能需要进行传参操作 如:免密登录,同步用户信息等
1.通过localStorage或sessionStorage进行参数传递,主应用进行保存数据子应用去获取数据
主应用进行数据保存
在子应页面中获取
可以清楚看到localStorage和sessionStorage是互通的
2.通过qiankun自带的initGlobalState方法传递
主应用设置 :
在主应用的src/qiankun 下新建一个initGlobalState文件内容如下
// 引入乾坤(qiankun)内置方法initGlobalState
import { initGlobalState } from 'qiankun'
// 进行初步设置
export const qiankunActions = initGlobalState({
// 事件触发来源(用来区分那个子应用进行传递的参数)
eventFrom: '',
// 事件的标识
eventCode: '',
// 事件传递的参数
eventDate: {}
})
然后在主应用页面开启监听
<script>
import { qiankunActions } from "@/qiankun/initGlobalState";
export default {
beforeMount() {
// state是新值 preState是旧值
qiankunActions.onGlobalStateChange((state, preState) => {
console.log("主应用监听到globalState发生变化", state, preState);
});
},
// 页面销毁时结束监听
beforeDestroy() {
qiankunActions.offGlobalStateChange();
},
};
</script>
子应用设置 :
在子应用的mian.js文件中有之前暴露的钩子函数,我们需要在mount函数中进行挂在方法如下
/**
* mount : 在应用每次进入时调用
*/
export async function mount(props) {
console.log('vue2-app mount', props);
// 将方法挂载到vue实列上面,方便后期使用
// setGlobalState用于设置参数
Vue.prototype.$setGlobalState = props.setGlobalState
// onGlobalStateChange用于开启子应用监听(开启后可以获取到你改变的参数)
Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange
render(props);
}
在要进行传参的页面进行调用如
methods: {
starst() {
this.$setGlobalState({
// 事件触发来源
eventFrom: "test",
// 事件的标识
eventCode: "test_Event",
// 事件传递的参数
eventDate: { name: "张三", age: "18", sex: "沃尔玛购物袋(不可定义)" },
});
},
starstGlobalStateListener() {
// 这个方法可以放到生命周期进行调用
this.$onGlobalStateChange((state) => {
console.log('子应用监听',state);
});
},
},
注意:子应用不可以创建一级属性,并且您会发现控制台会有这条警告,提示你不要使用globalState 它会在3.0版本删除这个API
3.通过eventBus方法实现
主应用设置 :
在主应用的src/qiankun 下新建一个eventBus文件内容如下
import Vue from 'vue'
// 这里只需要把vue实例抛出去
export const qiankunEventBus=new Vue()
在主应用页面设置方法
<script>
import { qiankunEventBus } from "@/qiankun/eventBus";
export default {
beforeMount() {
qiankunEventBus.$on('testEvent',(props)=>{
console.log('主应用EventBus监听到',props);
})
},
beforeDestroy() {
qiankunEventBus.$off()
},
};
</script>
eventBus方法配置类似于globalState不同的是要多一步传入 如下
import { qiankunEventBus } from './eventBus'
// 子应用列表
const apps = [
{
name: 'test', // 子应用app name 推荐与子应用的package的name一致
entry: '//localhost:6061/', // 子应用的入口地址,就是你子应用运行起来的地址
activeRule: '/children', // 子应用的路由前缀
container: '#qiankun-children', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】
props: { // 主应用向子应用传递的参数
time: new Date().getTime(),
qiankunEventBus
},
},
]
export default apps
子应用设置 :
我们需要在mount函数中进行挂在方法如下
/**
* mount : 在应用每次进入时调用
*/
export async function mount(props) {
console.log('vue2-app mount', props);
// 将方法挂载到vue实列上面,方便后期使用
Vue.prototype.$qiankunEventBus = props.qiankunEventBus
render(props);
}
在子页面使用
qiankunEventBus(){
// 参数和普通eventbus一样
this.$qiankunEventBus.$emit('testEvent', { name: "张三", age: "18", sex: "沃尔玛购物袋(不可定义)" })
}
4.使用vuex进行数据共享(推荐)
主应用设置:
先创建属于自己的模块让后传递给子应用达到数据共享,目录store/modules/common common内容如下:
export default {
namespaced: true, // 开启命名空间
state: {
userInfo: { name: "张三", age: 18, sex: "沃尔玛购物袋(不可定义)", describe: '资深宅男' }
},
mutations: {
set_userInfo(state, data) {
state.userInfo = data
}
},
actions: {},
getters: {
get_userInfo(state) {
let { name, age, sex, describe } = state.userInfo
return '姓名: ' + name + '年龄: ' + age + '性别: ' + sex + '描述: ' + describe
}
}
}
然后在qiankun/app 文件进行传递如下:
import qiankun_common from '@/store/modules/common'
// 子应用列表
const apps = [
{
name: 'test', // 子应用app name 推荐与子应用的package的name一致
entry: '//localhost:6061/', // 子应用的入口地址,就是你子应用运行起来的地址
activeRule: '/children', // 子应用的路由前缀
container: '#qiankun-children', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】
props: { // 主应用向子应用传递的参数
time: new Date().getTime(),
qiankun_common
},
},
]
export default apps
子应用设置:
需要在mian.js中的mount函数中进行模块注册如下:
/**
* mount : 在应用每次进入时调用
*/
export async function mount(props) {
console.log('vue2-app mount', props);
// 动态注册vuex模块
if (store && store.hasModule) {
// store.hasModule()方法判断是否存在此模块
if (!store.hasModule('qiankun_common')) {
// store.registerModule()用于动态添加模块
store.registerModule('qiankun_common', props.qiankun_common)
}
}
render(props);
}
最后在页面使用,考虑到子应用的单独运行所以在使用时需要加上环境判断
user() {
return ` 二、${
window.__POWERED_BY_QIANKUN__
? JSON.stringify(this.$store.state.qiankun_common.userInfo)
: "未传入"
}${
window.__POWERED_BY_QIANKUN__
? this.$store.getters["qiankun_common/get_userInfo"]
: "未传入"
}`;
},
效果展示:
在乾坤(qiankun)环境下效果:
子应用单独运行效果: