手把手教你搭建微前端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...

相关推荐
恋猫de小郭40 分钟前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
清岚_lxn5 小时前
原生SSE实现AI智能问答+Vue3前端打字机流效果
前端·javascript·人工智能·vue·ai问答
ZoeLandia6 小时前
Element UI 设置 el-table-column 宽度 width 为百分比无效
前端·ui·element-ui
橘子味的冰淇淋~6 小时前
解决 vite.config.ts 引入scss 预处理报错
前端·vue·scss
小小小小宇8 小时前
V8 引擎垃圾回收机制详解
前端
lauo8 小时前
智体知识库:ai-docs对分布式智体编程语言Poplang和javascript的语法的比较(知识库问答)
开发语言·前端·javascript·分布式·机器人·开源
拉不动的猪8 小时前
设计模式之------单例模式
前端·javascript·面试
一袋米扛几楼989 小时前
【React框架】什么是 Vite?如何使用vite自动生成react的目录?
前端·react.js·前端框架
Alt.99 小时前
SpringMVC基础二(RestFul、接收数据、视图跳转)
java·开发语言·前端·mvc
进取星辰9 小时前
1、从零搭建魔法工坊:React 19 新手村生存指南
前端·react.js·前端框架