手把手教你搭建微前端qiankun

前言

如果你还不知道什么是微前端,刚好你们公司需要接入微前端的话可以看看这篇文章 ,如有写的不好的地方,或者建议欢迎掘友们指正,相互学习,共同进步。

一.介绍微前端

微前端(Micro-Frontends)是一种前端架构模式,灵感来源于微服务架构。它将一个大型的前端应用拆分成多个小型、独立的前端模块,每个模块可以由不同的团队开发、部署和维护,最终在运行时通过某种方式整合成一个完整的应用。微前端的核心目标是解决大型前端项目的复杂性问题,提升开发效率和团队协作能力。


微前端的核心概念

  1. 独立性
    • 每个微前端模块是一个独立的小型前端应用,可以使用不同的技术栈(如 React、Vue、Angular)。
    • 模块之间通过约定的接口通信,互不干扰。
  2. 独立开发与部署
    • 每个微前端可以由独立的团队开发,拥有自己的代码库、构建流程和部署管道。
    • 某个模块的更新不会影响其他模块,降低耦合。
  3. 运行时整合
    • 在浏览器端通过某种机制(如 iframe、Web Components、JavaScript 动态加载)将多个微前端组合成一个完整的页面。

微前端的优点

  1. 技术栈无关: 不同团队可以选择最适合自己的框架或库(如 React 团队和 Vue 团队可以共存)。 便于技术升级,旧模块可以逐步替换而无需重写整个应用。

  2. 团队自治: 每个团队负责自己的模块,减少跨团队协作的沟通成本。

  3. 独立部署 : 某个模块更新时无需重新部署整个应用,降低上线风险。

微前端的缺点

  1. 复杂性增加: 模块之间的通信、样式隔离、路由管理等需要额外处理。

  2. 性能开销: 多个模块可能加载不同的框架,导致重复加载(如 React 和 Vue 同时加载)。 运行时整合可能增加网络请求和渲染时间。

  3. 一致性挑战: 不同模块的 UI/UX 可能不统一,影响用户体验。

  4. 学习成本: 团队需要学习微前端框架或工具(如 Qiankun、single-spa)。

二 .微前端的实现方式

以下是几种常见的微前端实现方式:

  1. iframe 嵌入

    • 每个微前端模块运行在独立的 iframe 中,隔离性强。
    • 缺点:性能差,iframe 通信复杂,用户体验(如页面跳转)不佳。
  2. Web Components

    • 将微前端模块封装为 Web Components,利用 Shadow DOM 实现样式和逻辑隔离。
    • 优点:原生支持,隔离性好。
    • 缺点:开发复杂,浏览器兼容性需考虑。
  3. JavaScript 动态加载

    • 通过动态加载脚本 (script 标签)加载不同模块。
    • 优点:灵活,易于实现。
    • 缺点:需要手动管理依赖和加载顺序。
  4. 微前端框架

    • 使用专门的微前端框架,如:

      • single-spa:最早的微前端框架,支持多种技术栈整合。
      • Qiankun:阿里开源,基于 single-spa,提供了更完善的解决方案(如沙箱隔离、预加载)。
      • MicroApp:京东开源,专注于简洁性和性能。

三. 搭建qiankun框架

搭建一个简单的基于 qiankun 的微前端框架示例,这个示例将包含一个主应用和一个子应用。

  1. 首先创建项目目录结构:
bash 复制代码
micro-frontend-demo/
├── main-app/          # 主应用
├── sub-app/          # 子应用
└── package.json
  1. 主应用 (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>
  1. 子应用配置 (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
  1. 当主应用props传递参数的时候,子应用在mount中调用actions.setActions(props)拿到属性,上方示例已写。
  2. 当子应用传入数据到主应用时候:
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...

相关推荐
Larcher31 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐43 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭1 小时前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu2 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端
六月的可乐2 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程