一、Micro-App 核心原理
1.1 技术架构
Micro-App 基于 Web Component 实现,是目前最轻量、最简单的微前端方案。
Micro-App 架构
├── Web Component
│ ├── Custom Element(自定义元素)
│ ├── Shadow DOM(样式隔离)
│ └── 类 Vue 组件使用
│
├── 沙箱隔离
│ ├── with + Proxy 沙箱
│ ├── iframe 沙箱(可选)
│ └── 自动样式隔离
│
└── 数据通信
├── Props 传递
├── 数据绑定
└── 事件通信
1.2 核心特点
1. 类组件化使用
<!-- 就像使用普通 Vue 组件一样 -->
<template>
<div>
<micro-app
name="app1"
url="http://localhost:8081/"
:data="appData"
@datachange="handleDataChange"
/>
</div>
</template>
2. 自动样式隔离
// Micro-App 自动处理样式隔离,无需配置
// 原理:利用 Web Component 的 Shadow DOM
3. 零改造接入
// 子应用几乎不需要改造
// 只需在 webpack 配置中设置 publicPath
// vue.config.js
module.exports = {
publicPath: window.__MICRO_APP_BASE_ROUTE__ || '/'
}
1.3 与其他方案对比
| 特性 | Micro-App | qiankun | Wujie |
|---|---|---|---|
| 接入难度 | 极低 | 中 | 低 |
| 学习成本 | 极低 | 中 | 低 |
| 包体积 | 极小(46KB) | 中(234KB) | 中(180KB) |
| 样式隔离 | 自动 | 需配置 | 天然 |
| JS 隔离 | with 沙箱 | Proxy 沙箱 | iframe 沙箱 |
| 子应用改造 | 几乎无 | 需要 | 零改造 |
| 适用场景 | 中小型项目 | 大型项目 | 强隔离需求 |
二、Micro-App 实战配置
2.1 主应用配置
安装
npm install @micro-zoe/micro-app
# or
yarn add @micro-zoe/micro-app
完整代码示例
<template>
<div id="main-app">
<!-- 导航菜单 -->
<nav class="main-nav">
<div class="logo">Micro-App 示例</div>
<ul class="menu">
<li
v-for="app in apps"
:key="app.name"
@click="switchApp(app.name)"
:class="{ active: currentApp === app.name }"
>
{{ app.title }}
</li>
</ul>
<div class="user-info">
<span>{{ userData.name }}</span>
<button @click="logout">退出</button>
</div>
</nav>
<!-- 主应用内容 -->
<div class="main-content">
<!-- 主应用首页 -->
<div v-if="currentApp === 'home'" class="home-page">
<h1>Micro-App 微前端示例</h1>
<p>这是最简单、最轻量的微前端方案</p>
<div class="features">
<div class="feature-card">
<h3>零学习成本</h3>
<p>像使用组件一样使用微前端</p>
</div>
<div class="feature-card">
<h3>自动样式隔离</h3>
<p>无需任何配置</p>
</div>
<div class="feature-card">
<h3>极小体积</h3>
<p>仅 46KB</p>
</div>
</div>
</div>
<!-- 子应用容器 -->
<micro-app
v-else
:name="currentApp"
:url="getAppUrl(currentApp)"
:data="microAppData"
:baseroute="getBaseroute(currentApp)"
@mounted="onAppMounted"
@unmount="onAppUnmount"
@datachange="onDataChange"
@error="onError"
inline
disableScopecss
disableSandbox
keep-alive
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue'
import microApp from '@micro-zoe/micro-app'
// 初始化 micro-app
onMounted(() => {
microApp.start({
// 全局配置
plugins: {
modules: {
// 子应用列表
'app1': [{
loader(code) {
// 可以对子应用代码进行处理
console.log('处理 app1 代码')
return code
}
}]
}
},
// 生命周期
lifeCycles: {
created(e) {
console.log('created', e)
},
beforemount(e) {
console.log('beforemount', e)
},
mounted(e) {
console.log('mounted', e)
},
unmount(e) {
console.log('unmount', e)
},
error(e) {
console.error('error', e)
}
},
// 预加载
preFetchApps: [
{ name: 'app1', url: 'http://localhost:8081/' }
],
// 全局数据
globalAssets: {
js: ['https://cdn.jsdelivr.net/npm/vue@3'],
css: []
}
})
})
const currentApp = ref('home')
const apps = [
{ name: 'home', title: '首页', url: '' },
{ name: 'app1', title: '子应用1', url: 'http://localhost:8081/' },
{ name: 'app2', title: '子应用2', url: 'http://localhost:8082/' },
{ name: 'app3', title: '子应用3', url: 'http://localhost:8083/' }
]
// 用户数据
const userData = reactive({
name: 'Admin',
role: 'admin',
token: 'xxxx-xxxx'
})
// 传递给子应用的数据
const microAppData = ref({
user: userData,
theme: 'light',
timestamp: Date.now()
})
// 切换应用
const switchApp = (appName) => {
currentApp.value = appName
}
// 获取应用 URL
const getAppUrl = (appName) => {
const app = apps.find(a => a.name === appName)
return app ? app.url : ''
}
// 获取 baseroute
const getBaseroute = (appName) => {
return `/${appName}`
}
// 子应用生命周期
const onAppMounted = (e) => {
console.log('子应用挂载完成', e)
}
const onAppUnmount = (e) => {
console.log('子应用卸载', e)
}
// 监听子应用数据变化
const onDataChange = (e) => {
console.log('子应用数据变化', e.detail.data)
// 处理子应用发来的数据
const data = e.detail.data
if (data.type === 'updateUser') {
Object.assign(userData, data.user)
}
}
const onError = (e) => {
console.error('子应用加载错误', e)
}
// 更新传递给子应用的数据
const updateMicroAppData = () => {
microAppData.value = {
...microAppData.value,
timestamp: Date.now()
}
}
// 监听用户数据变化,同步给子应用
watch(() => userData, (newVal) => {
microAppData.value = {
...microAppData.value,
user: { ...newVal }
}
}, { deep: true })
const logout = () => {
if (confirm('确定退出吗?')) {
userData.name = ''
userData.token = ''
currentApp.value = 'home'
}
}
</script>
<style scoped>
#main-app {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.main-nav {
display: flex;
align-items: center;
padding: 0 24px;
height: 64px;
background: #001529;
color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.logo {
font-size: 20px;
font-weight: bold;
margin-right: 50px;
}
.menu {
display: flex;
list-style: none;
margin: 0;
padding: 0;
flex: 1;
gap: 8px;
}
.menu li {
padding: 8px 20px;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
}
.menu li:hover {
background: rgba(255, 255, 255, 0.1);
}
.menu li.active {
background: #1890ff;
}
.user-info {
display: flex;
align-items: center;
gap: 16px;
}
.user-info button {
padding: 8px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.user-info button:hover {
background: #40a9ff;
}
.main-content {
flex: 1;
overflow: auto;
background: #f0f2f5;
}
.home-page {
padding: 40px;
max-width: 1200px;
margin: 0 auto;
}
.home-page h1 {
font-size: 36px;
color: #001529;
margin-bottom: 16px;
}
.home-page p {
font-size: 18px;
color: #666;
margin-bottom: 40px;
}
.features {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.feature-card {
background: white;
padding: 32px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
text-align: center;
transition: all 0.3s;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.feature-card h3 {
font-size: 20px;
color: #1890ff;
margin-bottom: 12px;
}
.feature-card p {
font-size: 14px;
color: #666;
margin: 0;
}
</style>
2.2 子应用配置
子应用改造(极少)
// 子应用 main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import routes from './router'
// 获取应用基础路径
const baseroute = window.__MICRO_APP_BASE_ROUTE__ || '/'
const router = createRouter({
history: createWebHistory(baseroute),
routes
})
const app = createApp(App)
app.use(router)
app.mount('#app')
// 就这么简单!子应用几乎不需要改造
子应用通信
<!-- 子应用组件 -->
<template>
<div class="sub-app">
<h2>子应用1</h2>
<!-- 显示主应用传来的数据 -->
<div class="data-panel">
<h3>主应用数据:</h3>
<pre>{{ mainAppData }}</pre>
</div>
<div class="actions">
<button @click="sendDataToMain">发送数据给主应用</button>
<button @click="updateUserInfo">更新用户信息</button>
</div>
<!-- 子应用自己的功能 -->
<div class="feature-content">
<h3>子应用功能列表</h3>
<ul>
<li v-for="item in features" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const mainAppData = ref({})
const features = ref([
{ id: 1, name: '功能1' },
{ id: 2, name: '功能2' },
{ id: 3, name: '功能3' }
])
// 监听主应用数据变化
const dataListener = (data) => {
console.log('收到主应用数据', data)
mainAppData.value = data
// 根据数据变化做相应处理
if (data.theme) {
document.body.setAttribute('data-theme', data.theme)
}
}
onMounted(() => {
// 方式1: 监听数据变化
if (window.microApp) {
// 获取主应用数据
mainAppData.value = window.microApp.getData()
// 监听数据变化
window.microApp.addDataListener(dataListener)
}
})
onUnmounted(() => {
// 卸载时移除监听
if (window.microApp) {
window.microApp.removeDataListener(dataListener)
}
})
// 发送数据给主应用
const sendDataToMain = () => {
if (window.microApp) {
window.microApp.dispatch({
type: 'notification',
message: '子应用发来的消息',
timestamp: Date.now()
})
}
}
// 更新用户信息
const updateUserInfo = () => {
if (window.microApp) {
window.microApp.dispatch({
type: 'updateUser',
user: {
name: 'Updated User',
role: 'super-admin'
}
})
}
}
</script>
<style scoped>
.sub-app {
padding: 24px;
}
h2 {
color: #001529;
margin-bottom: 24px;
}
.data-panel,
.feature-content {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 20px;
}
h3 {
font-size: 16px;
color: #333;
margin-bottom: 12px;
}
pre {
background: #f5f5f5;
padding: 12px;
border-radius: 4px;
font-size: 13px;
overflow-x: auto;
}
.actions {
display: flex;
gap: 12px;
margin: 20px 0;
}
.actions button {
padding: 10px 24px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.actions button:hover {
background: #40a9ff;
transform: translateY(-2px);
}
.feature-content ul {
list-style: none;
padding: 0;
}
.feature-content li {
padding: 12px;
background: #f0f2f5;
border-radius: 4px;
margin-bottom: 8px;
}
</style>
Webpack 配置
// vue.config.js
module.exports = {
devServer: {
port: 8081,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
publicPath: window.__MICRO_APP_BASE_ROUTE__ || '/',
configureWebpack: {
output: {
// Micro-App 不需要 UMD 格式
// 直接使用默认配置即可
}
}
}
2.3 高级特性
2.3.1 预加载
// 主应用
import microApp from '@micro-zoe/micro-app'
microApp.start({
preFetchApps: [
{
name: 'app1',
url: 'http://localhost:8081/',
// 预加载时机
level: 2, // 1=空闲时间 2=立即加载
// 是否默认开启沙箱
'default-page': '/home',
// 是否保活
'keep-alive': true
}
]
})
// 或手动预加载
microApp.preFetch([
{ name: 'app1', url: 'http://localhost:8081/' }
])
2.3.2 保活机制
<template>
<!-- keep-alive 属性开启保活 -->
<micro-app
name="app1"
url="http://localhost:8081/"
keep-alive
/>
</template>
<script setup>
// 保活模式下,应用切换不会销毁
// 再次显示时会快速恢复
// 适合需要保持状态的应用
</script>
2.3.3 样式隔离控制
<template>
<!-- disableScopecss: 关闭样式隔离 -->
<micro-app
name="app1"
url="http://localhost:8081/"
disableScopecss
/>
<!-- 默认开启样式隔离,子应用样式不会泄露 -->
<micro-app
name="app2"
url="http://localhost:8082/"
/>
</template>
2.3.4 JS 沙箱控制
<template>
<!-- disableSandbox: 关闭 JS 沙箱 -->
<micro-app
name="app1"
url="http://localhost:8081/"
disableSandbox
/>
<!-- iframe: 使用 iframe 沙箱(更强隔离) -->
<micro-app
name="app2"
url="http://localhost:8082/"
iframe
/>
</template>
2.3.5 路由模式
<template>
<!-- 默认模式:子应用路由独立 -->
<micro-app
name="app1"
url="http://localhost:8081/"
/>
<!-- 路由同步模式:子应用路由同步到主应用 -->
<micro-app
name="app2"
url="http://localhost:8082/"
:baseroute="`/app2`"
/>
</template>
2.4 完整应用示例
多应用管理
<template>
<div class="multi-app-container">
<div class="sidebar">
<div
v-for="app in apps"
:key="app.name"
class="app-item"
:class="{ active: activeApps.includes(app.name) }"
@click="toggleApp(app.name)"
>
<div class="app-icon">{{ app.icon }}</div>
<div class="app-name">{{ app.title }}</div>
<div v-if="activeApps.includes(app.name)" class="app-status">运行中</div>
</div>
</div>
<div class="app-container">
<div class="tabs">
<div
v-for="name in activeApps"
:key="name"
class="tab"
:class="{ active: currentApp === name }"
@click="switchToApp(name)"
>
{{ getAppTitle(name) }}
<span class="close" @click.stop="closeApp(name)">×</span>
</div>
</div>
<div class="app-content">
<micro-app
v-for="name in activeApps"
:key="name"
v-show="currentApp === name"
:name="name"
:url="getAppUrl(name)"
:data="appData"
keep-alive
@mounted="onAppMounted(name)"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const apps = [
{ name: 'app1', title: '应用1', icon: '📊', url: 'http://localhost:8081/' },
{ name: 'app2', title: '应用2', icon: '📈', url: 'http://localhost:8082/' },
{ name: 'app3', title: '应用3', icon: '📉', url: 'http://localhost:8083/' }
]
const activeApps = ref([])
const currentApp = ref('')
const appData = reactive({
user: { name: 'Admin' },
token: 'xxxx'
})
const toggleApp = (name) => {
if (activeApps.value.includes(name)) {
closeApp(name)
} else {
activeApps.value.push(name)
currentApp.value = name
}
}
const switchToApp = (name) => {
currentApp.value = name
}
const closeApp = (name) => {
const index = activeApps.value.indexOf(name)
if (index > -1) {
activeApps.value.splice(index, 1)
if (currentApp.value === name) {
currentApp.value = activeApps.value[activeApps.value.length - 1] || ''
}
}
}
const getAppTitle = (name) => {
return apps.find(app => app.name === name)?.title || name
}
const getAppUrl = (name) => {
return apps.find(app => app.name === name)?.url || ''
}
const onAppMounted = (name) => {
console.log(`${name} 挂载完成`)
}
</script>
<style scoped>
.multi-app-container {
display: flex;
height: 100vh;
}
.sidebar {
width: 240px;
background: #001529;
padding: 20px 0;
}
.app-item {
padding: 16px 24px;
color: rgba(255, 255, 255, 0.65);
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 12px;
}
.app-item:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.app-item.active {
background: #1890ff;
color: white;
}
.app-icon {
font-size: 24px;
}
.app-name {
flex: 1;
font-size: 14px;
}
.app-status {
font-size: 12px;
padding: 2px 8px;
background: rgba(82, 196, 26, 0.2);
border-radius: 10px;
color: #52c41a;
}
.app-container {
flex: 1;
display: flex;
flex-direction: column;
}
.tabs {
display: flex;
background: #f0f2f5;
border-bottom: 1px solid #d9d9d9;
}
.tab {
padding: 12px 24px;
background: white;
border-right: 1px solid #d9d9d9;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.3s;
}
.tab:hover {
background: #fafafa;
}
.tab.active {
background: white;
border-bottom: 2px solid #1890ff;
color: #1890ff;
}
.tab .close {
font-size: 18px;
opacity: 0.5;
transition: all 0.2s;
}
.tab .close:hover {
opacity: 1;
color: #ff4d4f;
}
.app-content {
flex: 1;
overflow: hidden;
}
</style>
三、使用技巧
3.1 全局数据共享
// 主应用定义全局数据
import microApp from '@micro-zoe/micro-app'
microApp.setGlobalData({ token: 'xxxx' })
// 子应用获取全局数据
const globalData = window.microApp.getGlobalData()
3.2 路由守卫
// 主应用路由守卫
router.beforeEach((to, from, next) => {
// 检查子应用权限
if (to.path.startsWith('/app1')) {
const hasPermission = checkPermission('app1')
if (!hasPermission) {
next('/403')
return
}
}
next()
})
3.3 错误处理
<template>
<micro-app
name="app1"
url="http://localhost:8081/"
@error="handleError"
/>
</template>
<script setup>
const handleError = (e) => {
console.error('子应用加载错误', e)
// 可以显示错误页面或重试
if (confirm('子应用加载失败,是否重试?')) {
window.location.reload()
}
}
</script>
四、简历撰写指南
4.1 项目经验描述模板
项目名称: Micro-App 轻量级微前端实践
项目时间: 2024.08 - 2024.11
项目描述: 负责中小型管理系统的微前端改造,采用 Micro-App 框架快速实现应用拆分。通过类组件化的使用方式,实现了零学习成本的微前端接入,显著提升了开发效率和系统可维护性。
核心职责
- 技术选型:综合考虑项目规模、团队能力、接入成本,选择 Micro-App 轻量方案
- 架构设计:设计主应用框架,实现子应用注册、数据通信、权限控制
- 快速接入:指导团队完成 5 个子应用的改造,平均接入时间仅 2 小时
- 性能优化:实现子应用预加载、保活机制,切换速度提升 70%
技术栈: Micro-App、Vue3、Web Component、Shadow DOM
项目成果
- 接入时间从 qiankun 的 2 天缩短到 2 小时(降低 90%)
- 包体积仅 46KB,比 qiankun 小 80%
- 样式隔离自动处理,零配置
- 子应用几乎零改造,开发体验极佳
- 系统维护成本降低 50%
4.2 SOP 标准回答话术
面试官:为什么选择 Micro-App 而不是 qiankun?
回答话术: "我们项目选择 Micro-App 主要基于以下考虑:
首先是项目规模。我们是一个中型管理系统,不是特别复杂,不需要 qiankun 那么重的方案。Micro-App 只有 46KB,qiankun 有 234KB,对我们来说 Micro-App 更轻量。
第二是接入成本。Micro-App 的使用方式就像普通 Vue 组件,写个
第三是子应用改造。Micro-App 子应用几乎不需要改造,只要改一下 publicPath 就可以了。qiankun 需要导出生命周期函数、改 webpack 配置等,改造成本高。我们有几个老项目,改动越少越好。
第四是样式隔离。Micro-App 基于 Web Component,样式隔离是自动的,不需要任何配置。qiankun 需要配置 Shadow DOM 或 Scoped CSS,还可能遇到组件库不兼容的问题。
当然 Micro-App 也有劣势,比如功能相对简单、社区较小、复杂场景支持不足。但对我们的场景来说,这些劣势影响不大。我们更看重简单、轻量、易用。
最终效果很好,5 个子应用平均接入时间只要 2 小时,团队反馈开发体验非常好。"
4.3 难点与亮点分析
难点1:子应用通信
虽然 Micro-App 提供了数据通信,但相比 qiankun 功能较弱。
解决方案
// 封装通信工具类
class MicroAppBridge {
constructor(appName) {
this.appName = appName
this.listeners = {}
}
// 发送消息
emit(event, data) {
if (window.microApp) {
window.microApp.dispatch({
event,
data,
from: this.appName
})
}
}
// 监听消息
on(event, handler) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event].push(handler)
}
// 处理消息
handleMessage(message) {
const { event, data } = message
if (this.listeners[event]) {
this.listeners[event].forEach(handler => handler(data))
}
}
}
// 使用
const bridge = new MicroAppBridge('app1')
bridge.on('user-updated', (user) => {
console.log('用户更新', user)
})
亮点1:极简接入流程
开发了一键接入工具:
# 一键生成子应用
micro-app create my-app
# 自动配置 publicPath
# 自动添加通信代码
# 自动生成文档
# 接入时间从 2 天降到 2 小时
亮点2:保活机制优化
针对频繁切换的应用,使用保活:
<micro-app
name="frequent-app"
keep-alive
/>
<!-- 切换速度提升 70% -->
4.4 完整简历示例
【Micro-App 轻量级微前端实践】2024.08 - 2024.11
项目背景:
中型管理系统需要拆分为独立模块,降低耦合度,提升开发效率。考虑团队规模和技术能力,需要简单易用的微前端方案。
技术选型:
对比 qiankun、Micro-App、Wujie 后选择 Micro-App,原因:
- 轻量级(46KB),适合中小型项目
- 接入简单,学习成本低
- 类组件化使用,开发体验好
- 自动样式隔离,零配置
核心工作:
1. 快速接入实践
- 制定子应用接入规范(仅需改 publicPath)
- 开发接入工具,一键生成子应用模板
- 指导团队完成 5 个子应用接入,平均耗时 2 小时
2. 通信机制设计
- 封装统一的通信工具类,简化主子应用数据传递
- 实现全局数据共享机制
- 开发事件总线,支持跨应用通信
3. 性能优化
- 配置子应用预加载,首次访问速度提升 50%
- 启用保活机制,应用切换速度提升 70%
- 实现懒加载,减少初始加载时间
4. 工程化建设
- 开发脚手架工具,标准化子应用创建流程
- 编写最佳实践文档和示例代码
- 建立代码审查规范
技术栈:
Micro-App、Vue3、Web Component、Shadow DOM
项目成果:
- 接入时间:从 2 天缩短到 2 小时(降低 90%)
- 包体积:仅 46KB,比 qiankun 小 80%
- 学习成本:零学习成本,团队满意度 95%
- 开发效率:各模块独立开发,效率提升 40%
- 维护成本:系统维护成本降低 50%
五、总结
Micro-App 是最简单、最轻量的微前端方案,非常适合:
- 中小型项目
- 追求快速接入
- 团队技术能力一般
- 不需要复杂功能
核心优势
- 零学习成本(类组件使用)
- 极小体积(46KB)
- 自动样式隔离
- 子应用几乎零改造
适用场景
- 快速搭建微前端系统
- 技术栈统一的项目
- 对性能要求不是特别高
- 追求简单易用