前言
如果你还不知道什么是微前端,刚好你们公司需要接入微前端的话可以看看这篇文章 ,如有写的不好的地方,或者建议欢迎掘友们指正,相互学习,共同进步。
一.介绍微前端
微前端(Micro-Frontends)是一种前端架构模式,灵感来源于微服务架构。它将一个大型的前端应用拆分成多个小型、独立的前端模块,每个模块可以由不同的团队开发、部署和维护,最终在运行时通过某种方式整合成一个完整的应用。微前端的核心目标是解决大型前端项目的复杂性问题,提升开发效率和团队协作能力。
微前端的核心概念
- 独立性 :
- 每个微前端模块是一个独立的小型前端应用,可以使用不同的技术栈(如 React、Vue、Angular)。
- 模块之间通过约定的接口通信,互不干扰。
- 独立开发与部署 :
- 每个微前端可以由独立的团队开发,拥有自己的代码库、构建流程和部署管道。
- 某个模块的更新不会影响其他模块,降低耦合。
- 运行时整合 :
- 在浏览器端通过某种机制(如 iframe、Web Components、JavaScript 动态加载)将多个微前端组合成一个完整的页面。
微前端的优点
-
技术栈无关: 不同团队可以选择最适合自己的框架或库(如 React 团队和 Vue 团队可以共存)。 便于技术升级,旧模块可以逐步替换而无需重写整个应用。
-
团队自治: 每个团队负责自己的模块,减少跨团队协作的沟通成本。
-
独立部署 : 某个模块更新时无需重新部署整个应用,降低上线风险。
微前端的缺点
-
复杂性增加: 模块之间的通信、样式隔离、路由管理等需要额外处理。
-
性能开销: 多个模块可能加载不同的框架,导致重复加载(如 React 和 Vue 同时加载)。 运行时整合可能增加网络请求和渲染时间。
-
一致性挑战: 不同模块的 UI/UX 可能不统一,影响用户体验。
-
学习成本: 团队需要学习微前端框架或工具(如 Qiankun、single-spa)。
二 .微前端的实现方式
以下是几种常见的微前端实现方式:
-
iframe 嵌入:
- 每个微前端模块运行在独立的 iframe 中,隔离性强。
- 缺点:性能差,iframe 通信复杂,用户体验(如页面跳转)不佳。
-
Web Components:
- 将微前端模块封装为 Web Components,利用 Shadow DOM 实现样式和逻辑隔离。
- 优点:原生支持,隔离性好。
- 缺点:开发复杂,浏览器兼容性需考虑。
-
JavaScript 动态加载:
- 通过动态加载脚本 (script 标签)加载不同模块。
- 优点:灵活,易于实现。
- 缺点:需要手动管理依赖和加载顺序。
-
微前端框架:
-
使用专门的微前端框架,如:
- single-spa:最早的微前端框架,支持多种技术栈整合。
- Qiankun:阿里开源,基于 single-spa,提供了更完善的解决方案(如沙箱隔离、预加载)。
- MicroApp:京东开源,专注于简洁性和性能。
-
三. 搭建qiankun框架
搭建一个简单的基于 qiankun 的微前端框架示例,这个示例将包含一个主应用和一个子应用。
- 首先创建项目目录结构:
bash
micro-frontend-demo/
├── main-app/ # 主应用
├── sub-app/ # 子应用
└── package.json
- 主应用 (main-app) 配置:
php
//main-app/vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue(),],
base: '/',
server: {
port: 3000,
},
});
// main-app/src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { registerMicroApps, start } from 'qiankun';
// 注册子应用
registerMicroApps([
{
name: 'subApp',
entry: '//localhost:3001', // 子应用地址
container: '#subapp-container',
activeRule: '/sub-app',
props:{
token
}
},
]);
// 启动 qiankun
start();
app.mount('#app');
xml
#### main-app/src/App.vue
<template>
<div>
<h1>主应用 (Vue 3 + TS + Vite)</h1>
<div id="subapp-container"></div>
</div>
</template>
<script setup lang="ts">
// 无需额外逻辑
</script>
<style scoped>
#subapp-container {
border: 1px solid #ccc;
padding: 10px;
margin-top: 20px;
}
</style>
- 子应用配置 (sub-app):
javascript
#### sub-app/vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun-lite';
export default defineConfig({
plugins: [vue(),qiankun({ name: 'sub-app', sandbox: true, }),],
base: '//localhost:3001/',
server: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*', // 解决开发环境跨域
},
proxy:{###代理}
build: {
// 配置 UMD 输出以兼容 qiankun(可选没用插件vite-plugin-qiankun-lite就不要写成umd)
//rollupOptions: {
// output: {
// format: 'umd',
// name: 'subApp',
// },
},
},
});
#### sub-app/src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import actions from '/@/utils/actions';
//xxxx还有一些导入不赘述了,只展示主要的
let app: ReturnType<typeof createApp> | null = null;
// 异步渲染函数
async function render(props: QiankunProps = {}) {
const { container } = props
app = createApp(App)
// 使用路由并等待初始化完成
app.use(router)
await router.isReady() // 确保路由就绪
// 注册插件和全局组件
//xxxx一些挂载
app.component('FontIcon', FontIcon)
// 挂载应用
app.mount(container ? container.querySelector('#app')! : '#app')
}
console.log('当前运行环境:', qiankunWindow.__POWERED_BY_QIANKUN__ ? 'Qiankun 微前端' : '独立模式')
// 独立运行时
if (!qiankunWindow?.__POWERED_BY_QIANKUN__) {
render() // 直接调用异步渲染
}
window.addEventListener('unhandledrejection', function browserRejectionHandler(event) { event && event.preventDefault(); console.error('未处理的 Promise 拒绝:', event.reason); // 记录错误详情 });
// qiankun 生命周期函数
export async function bootstrap() {
console.log('Vue3 子应用 bootstrap')
return Promise.resolve()
}
export async function mount(props: QiankunProps) {
console.log('Vue3 子应用 mount', props, actions)
// 处理主应用传来的 props
if (props) {
console.log(actions)
actions.setActions(props)
}
// 渲染应用
await render(props)
return Promise.resolve()
}
export async function unmount() {
console.log('Vue3 子应用 unmount')
// 敲重点手动清还是配置
//清理样式标签这边
document.querySelectorAll('style, link[rel="stylesheet"]').forEach(tag => {
if (tag.getAttribute('href')?.includes('child-app')) {
tag.remove()
}
})
// 卸载 Vue 实例
if (app) {
app.unmount()
app = null
}
return Promise.resolve()
}
js
上面是一个简单的示例,下面介绍主应用和子应用之间的通信
首先子应用新建一个actions文件:
typescript
// 定义 Actions 的方法
interface ActionMethods {
[key: string]: any
onGlobalStateChange: (...args: any[]) => void // 根据实际需求调整参数类型
setGlobalState: (...args: any[]) => void // 根据实际需求调整参数类型
}
class Actions {
actions: ActionMethods = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction,
}
constructor() {}
// 设置 actions,确保传入的对象符合 ActionMethods 类型
setActions(actions: ActionMethods) {
this.actions = actions
}
// 映射方法,TypeScript 可自动推断类型
onGlobalStateChange(...args: Parameters<ActionMethods['onGlobalStateChange']>) {
return this.actions.onGlobalStateChange(...args)
}
setGlobalState(...args: Parameters<ActionMethods['setGlobalState']>) {
// console.log(this.actions)
return this.actions.setGlobalState(...args)
}
}
function emptyAction() {
// 警告:提示当前使用的是空 Action
console.warn('Current execute action is empty!')
}
const actions = new Actions()
export default actions
- 当主应用props传递参数的时候,子应用在mount中调用actions.setActions(props)拿到属性,上方示例已写。
- 当子应用传入数据到主应用时候:
xml
//主应用vue文件:
<script setup lang="ts" name="upload">
import { loadMicroApp } from 'qiankun'
import { onBeforeUnmount } from 'vue'
import { initGlobalState } from 'qiankun'
const route = useRoute()
const router = useRouter()
const actions = initGlobalState({
type: null, // 初始化全局状态
data: null // 数据返回
})
actions.onGlobalStateChange(args => {
console.log('onGlobalStateChange', args)
if (args.type === 'success') {
//处理子应用传递的数据
}
})
//另一种加载方式
let microApp = loadMicroApp(
{
name: 'tjs-complaint-front',
entry: import.meta.env.VITE_MATERIAL_URL,
container: '#container',
props: {
}
},
{
singular: true // 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 false。
//样式隔离的方式:
// sandbox: {
// strictStyleIsolation: true // 正确放置 sandbox 配置
// // experimentalStyleIsolation: true
// // excludeAssetFilter: (url) => url.includes('@vite/client'),
// }
}
)
onBeforeUnmount(() => {
microApp.unmount()
microApp = null
})
</script>
<template>
<div id="container"></div>
</template>
xml
//子应用文件
<script setup lang="ts" name="test">
import actions from '/@/utils/actions'
const submit = () => {
console.log('test')
actions.setGlobalState({ type: 'success', data: 'hhhh' })
}
</script>
<template>
<div @click="submit">test</div>
</template>
<style lang="scss" scoped></style>
以上内容基本可以帮助你完成一个简单微前端搭建方法,当然还有一些细节没有具体解释,比如加载的方式有两种 registerMicroApps和loadMicroApp方式,既然看了这篇文章我想大概你也会点进官网看一看qiankun.umijs.org/zh/guide/ge...