一、子应用开发规范
1.1 生命周期钩子规范
// 子应用 main.js 标准模板
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import routes from './router'
import './public-path' // 公共路径配置
let instance = null
let router = null
let history = null
/**
* 渲染函数
* @param {Object} props - 主应用传递的参数
*/
function render(props = {}) {
const { container, routerBase } = props
// 创建路由实例
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? routerBase : '/')
router = createRouter({
history,
routes
})
// 创建Vue实例
instance = createApp(App)
instance.use(router)
// 挂载到容器
const mountNode = container
? container.querySelector('#app')
: document.querySelector('#app')
instance.mount(mountNode)
}
/**
* 生命周期:应用首次加载时调用,只会调用一次
*/
export async function bootstrap() {
console.log('[子应用] bootstrap')
// 这里可以做一些全局初始化工作
// 比如:
// - 设置全局错误处理
// - 初始化监控 SDK
// - 加载全局配置
}
/**
* 生命周期:应用每次激活时调用
* @param {Object} props - 主应用传递的参数
*/
export async function mount(props) {
console.log('[子应用] mount', props)
// 保存主应用传递的props
window.__MAIN_APP_PROPS__ = props
// 监听全局状态变化
if (props.onGlobalStateChange) {
props.onGlobalStateChange((state, prev) => {
console.log('[子应用] 全局状态变化', state, prev)
// 可以同步到子应用的 store
}, true)
}
// 渲染应用
render(props)
}
/**
* 生命周期:应用每次失活/卸载时调用
*/
export async function unmount() {
console.log('[子应用] unmount')
// 销毁Vue实例
if (instance) {
instance.unmount()
instance = null
}
// 清理路由
if (router) {
router = null
}
if (history) {
history = null
}
// 清理全局变量
window.__MAIN_APP_PROPS__ = null
}
/**
* 生命周期:主应用 props 更新时调用(可选)
* @param {Object} props - 更新后的 props
*/
export async function update(props) {
console.log('[子应用] update', props)
window.__MAIN_APP_PROPS__ = props
}
/**
* 独立运行模式
*/
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
1.2 环境判断规范
// utils/env.js
/**
* 判断是否在微前端环境运行
*/
export function isMicroApp() {
return window.__POWERED_BY_QIANKUN__ ||
window.__MICRO_APP_ENVIRONMENT__ ||
window.$wujie
}
/**
* 获取微前端类型
*/
export function getMicroAppType() {
if (window.__POWERED_BY_QIANKUN__) return 'qiankun'
if (window.__MICRO_APP_ENVIRONMENT__) return 'micro-app'
if (window.$wujie) return 'wujie'
return 'standalone'
}
/**
* 获取基础路径
*/
export function getBasePath() {
if (isMicroApp()) {
return window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ ||
window.__MICRO_APP_BASE_ROUTE__ ||
'/'
}
return process.env.BASE_URL || '/'
}
/**
* 获取 API 基础地址
*/
export function getApiBaseURL() {
if (isMicroApp()) {
// 微前端环境,使用主应用提供的 API 地址
return window.__MAIN_APP_PROPS__?.apiBaseURL || '/api'
}
// 独立运行,使用环境变量
return process.env.VUE_APP_API_BASE_URL || '/api'
}
/**
* 获取主应用传递的数据
*/
export function getMainAppData() {
return window.__MAIN_APP_PROPS__ || {}
}
// 使用示例
import { isMicroApp, getApiBaseURL } from '@/utils/env'
if (isMicroApp()) {
console.log('运行在微前端环境')
}
const api = axios.create({
baseURL: getApiBaseURL()
})
1.3 导出格式规范
// vue.config.js
const { name } = require('./package.json')
module.exports = {
devServer: {
port: 8081,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
// 库名称,唯一
library: `${name}-[name]`,
// UMD 格式
libraryTarget: 'umd',
// webpack jsonp 函数名,唯一
jsonpFunction: `webpackJsonp_${name}`,
// 全局变量名
globalObject: 'window'
}
}
}
1.4 publicPath 配置规范
// public-path.js
// qiankun 环境
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
// micro-app 环境
else if (window.__MICRO_APP_ENVIRONMENT__) {
__webpack_public_path__ = window.__MICRO_APP_BASE_ROUTE__
}
// wujie 环境
else if (window.$wujie) {
__webpack_public_path__ = window.$wujie.props.publicPath || '/'
}
// 独立运行
else {
__webpack_public_path__ = '/'
}
// main.js 第一行引入
import './public-path'
二、基座应用设计
2.1 统一 Layout
<template>
<div class="main-app-layout">
<!-- 顶部导航 -->
<header class="main-header">
<div class="logo">
<img src="@/assets/logo.png" alt="Logo">
<span>微前端平台</span>
</div>
<nav class="main-nav">
<a
v-for="menu in menus"
:key="menu.path"
:href="menu.path"
:class="{ active: isActive(menu.path) }"
@click.prevent="navigate(menu.path)"
>
{{ menu.name }}
</a>
</nav>
<div class="user-area">
<el-dropdown @command="handleCommand">
<span class="user-info">
<img :src="userInfo.avatar" class="avatar">
<span>{{ userInfo.name }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile">个人中心</el-dropdown-item>
<el-dropdown-item command="settings">设置</el-dropdown-item>
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</header>
<!-- 侧边栏(可选) -->
<aside class="main-aside" v-if="showAside">
<el-menu
:default-active="currentPath"
@select="handleMenuSelect"
>
<el-menu-item
v-for="item in sideMenus"
:key="item.path"
:index="item.path"
>
<span>{{ item.name }}</span>
</el-menu-item>
</el-menu>
</aside>
<!-- 主内容区 -->
<main class="main-content" :class="{ 'has-aside': showAside }">
<!-- 面包屑 -->
<div class="breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item
v-for="item in breadcrumbs"
:key="item.path"
:to="{ path: item.path }"
>
{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 子应用容器 -->
<div id="subapp-container" class="subapp-wrapper"></div>
<!-- 主应用路由 -->
<router-view v-if="!isSubApp"></router-view>
</main>
<!-- 页脚(可选) -->
<footer class="main-footer">
<p>© 2024 微前端平台. All rights reserved.</p>
</footer>
<!-- 全局加载提示 -->
<div v-if="loading" class="global-loading">
<el-loading text="加载中..." />
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const loading = ref(false)
const currentPath = ref(route.path)
// 顶部菜单
const menus = ref([
{ name: '首页', path: '/' },
{ name: '应用1', path: '/app1' },
{ name: '应用2', path: '/app2' },
{ name: '应用3', path: '/app3' }
])
// 侧边栏菜单
const sideMenus = ref([])
// 面包屑
const breadcrumbs = computed(() => {
const paths = route.path.split('/').filter(Boolean)
return paths.map((path, index) => ({
name: path,
path: '/' + paths.slice(0, index + 1).join('/')
}))
})
// 用户信息
const userInfo = computed(() => userStore.userInfo)
// 是否显示侧边栏
const showAside = computed(() => {
return route.path.startsWith('/app')
})
// 是否是子应用路由
const isSubApp = computed(() => {
return route.path.startsWith('/app')
})
// 导航
const navigate = (path) => {
router.push(path)
}
// 菜单选择
const handleMenuSelect = (path) => {
router.push(path)
}
// 判断菜单是否激活
const isActive = (path) => {
return route.path.startsWith(path)
}
// 用户操作
const handleCommand = (command) => {
switch (command) {
case 'profile':
router.push('/profile')
break
case 'settings':
router.push('/settings')
break
case 'logout':
logout()
break
}
}
const logout = () => {
userStore.logout()
router.push('/login')
}
// 监听路由变化
watch(() => route.path, (newPath) => {
currentPath.value = newPath
})
</script>
<style scoped>
.main-app-layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.main-header {
display: flex;
align-items: center;
height: 64px;
padding: 0 24px;
background: #001529;
color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 20px;
font-weight: bold;
}
.logo img {
height: 32px;
}
.main-nav {
display: flex;
gap: 8px;
flex: 1;
margin-left: 50px;
}
.main-nav a {
padding: 8px 20px;
color: rgba(255, 255, 255, 0.65);
text-decoration: none;
border-radius: 4px;
transition: all 0.3s;
}
.main-nav a:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.main-nav a.active {
background: #1890ff;
color: white;
}
.user-area {
display: flex;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s;
}
.user-info:hover {
background: rgba(255, 255, 255, 0.1);
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
.main-aside {
width: 200px;
background: white;
box-shadow: 2px 0 8px rgba(0,0,0,0.08);
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background: #f0f2f5;
overflow: hidden;
}
.main-content.has-aside {
margin-left: 200px;
}
.breadcrumb {
padding: 16px 24px;
background: white;
border-bottom: 1px solid #e8e8e8;
}
.subapp-wrapper {
flex: 1;
overflow: auto;
}
.main-footer {
padding: 16px 24px;
text-align: center;
background: white;
border-top: 1px solid #e8e8e8;
color: #666;
font-size: 14px;
}
.global-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
</style>
2.2 导航菜单管理
// stores/menu.js
import { defineStore } from 'pinia'
export const useMenuStore = defineStore('menu', {
state: () => ({
menus: [],
permissions: []
}),
getters: {
// 根据权限过滤菜单
visibleMenus() {
return this.menus.filter(menu => {
return !menu.permission || this.permissions.includes(menu.permission)
})
}
},
actions: {
// 加载菜单配置
async loadMenus() {
try {
const res = await fetch('/api/menus')
const data = await res.json()
this.menus = data.menus
this.permissions = data.permissions
} catch (error) {
console.error('加载菜单失败', error)
}
},
// 动态注册子应用菜单
registerAppMenus(appName, menus) {
const existingIndex = this.menus.findIndex(m => m.app === appName)
if (existingIndex > -1) {
this.menus[existingIndex].children = menus
} else {
this.menus.push({
app: appName,
children: menus
})
}
}
}
})
// 使用示例
const menuStore = useMenuStore()
// 主应用启动时加载菜单
await menuStore.loadMenus()
// 子应用注册自己的菜单
menuStore.registerAppMenus('app1', [
{ name: '功能1', path: '/app1/feature1' },
{ name: '功能2', path: '/app1/feature2' }
])
2.3 用户信息共享
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: null,
permissions: []
}),
getters: {
isLoggedIn() {
return !!this.token
},
hasPermission() {
return (permission) => {
return this.permissions.includes(permission)
}
}
},
actions: {
async login(username, password) {
try {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const data = await res.json()
this.userInfo = data.userInfo
this.token = data.token
this.permissions = data.permissions
// 保存到 localStorage
localStorage.setItem('token', data.token)
// 同步到全局状态
if (window.__QIANKUN_ACTIONS__) {
window.__QIANKUN_ACTIONS__.setGlobalState({
user: this.userInfo,
token: this.token
})
}
return true
} catch (error) {
console.error('登录失败', error)
return false
}
},
logout() {
this.userInfo = null
this.token = null
this.permissions = []
localStorage.removeItem('token')
// 通知所有子应用
if (window.__QIANKUN_ACTIONS__) {
window.__QIANKUN_ACTIONS__.setGlobalState({
user: null,
token: null
})
}
},
async loadUserInfo() {
const token = localStorage.getItem('token')
if (!token) return false
try {
const res = await fetch('/api/user/info', {
headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()
this.userInfo = data.userInfo
this.token = token
this.permissions = data.permissions
return true
} catch (error) {
console.error('加载用户信息失败', error)
this.logout()
return false
}
}
}
})
2.4 权限控制
// router/permission.js
import router from './index'
import { useUserStore } from '@/stores/user'
// 白名单路由
const whiteList = ['/', '/login', '/404']
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
// 已登录
if (userStore.isLoggedIn) {
if (to.path === '/login') {
next('/')
} else {
// 检查权限
if (to.meta.permission) {
if (userStore.hasPermission(to.meta.permission)) {
next()
} else {
next('/403')
}
} else {
next()
}
}
}
// 未登录
else {
if (whiteList.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
// 子应用权限检查
export function checkAppPermission(appName) {
const userStore = useUserStore()
const permission = `app:${appName}`
return userStore.hasPermission(permission)
}
2.5 错误边界
<!-- components/ErrorBoundary.vue -->
<template>
<div v-if="error" class="error-boundary">
<div class="error-icon">⚠️</div>
<h2>应用加载失败</h2>
<p class="error-message">{{ error.message }}</p>
<div class="error-actions">
<button @click="retry" class="btn-retry">重试</button>
<button @click="goHome" class="btn-home">返回首页</button>
</div>
<details v-if="showDetails">
<summary>错误详情</summary>
<pre>{{ error.stack }}</pre>
</details>
</div>
<slot v-else></slot>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const error = ref(null)
const showDetails = ref(process.env.NODE_ENV === 'development')
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误', err, info)
error.value = err
// 上报错误
reportError(err, info)
return false
})
const retry = () => {
error.value = null
window.location.reload()
}
const goHome = () => {
error.value = null
router.push('/')
}
const reportError = (err, info) => {
// 上报到监控系统
if (window.__MONITOR__) {
window.__MONITOR__.captureError(err, {
component: info,
type: 'vue-error'
})
}
}
</script>
<style scoped>
.error-boundary {
padding: 60px 20px;
text-align: center;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.error-icon {
font-size: 72px;
margin-bottom: 20px;
}
h2 {
color: #ff4d4f;
margin-bottom: 12px;
}
.error-message {
color: #666;
margin-bottom: 24px;
}
.error-actions {
display: flex;
gap: 12px;
justify-content: center;
}
.btn-retry,
.btn-home {
padding: 10px 24px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-retry {
background: #1890ff;
color: white;
}
.btn-retry:hover {
background: #40a9ff;
}
.btn-home {
background: #f5f5f5;
color: #666;
}
.btn-home:hover {
background: #e8e8e8;
}
details {
margin-top: 24px;
text-align: left;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
pre {
padding: 12px;
background: #f5f5f5;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
}
</style>
三、部署与发布
3.1 独立部署
# 部署脚本示例 deploy.sh
#!/bin/bash
APP_NAME=$1
ENV=$2
if [ -z "$APP_NAME" ] || [ -z "$ENV" ]; then
echo "用法: ./deploy.sh <app-name> <env>"
echo "示例: ./deploy.sh app1 production"
exit 1
fi
echo "开始部署 $APP_NAME 到 $ENV 环境..."
# 1. 构建应用
cd $APP_NAME
npm run build:$ENV
# 2. 上传到 CDN/OSS
echo "上传静态资源..."
aws s3 sync dist/ s3://my-bucket/$APP_NAME/$ENV/ --delete
# 3. 更新配置
echo "更新配置..."
curl -X POST https://api.example.com/update-config \
-H "Content-Type: application/json" \
-d "{\"app\": \"$APP_NAME\", \"env\": \"$ENV\", \"version\": \"$(git rev-parse --short HEAD)\"}"
# 4. 清理 CDN 缓存
echo "清理 CDN 缓存..."
aws cloudfront create-invalidation \
--distribution-id E123456789 \
--paths "/$APP_NAME/$ENV/*"
echo "部署完成!"
3.2 版本管理策略
// version-manager.js
class VersionManager {
constructor() {
this.versions = new Map()
}
// 注册应用版本
register(appName, version, entry) {
if (!this.versions.has(appName)) {
this.versions.set(appName, [])
}
this.versions.get(appName).push({
version,
entry,
timestamp: Date.now()
})
}
// 获取最新版本
getLatest(appName) {
const versions = this.versions.get(appName)
if (!versions || versions.length === 0) {
return null
}
return versions[versions.length - 1]
}
// 获取指定版本
getVersion(appName, version) {
const versions = this.versions.get(appName)
return versions?.find(v => v.version === version)
}
// 回滚到上一版本
rollback(appName) {
const versions = this.versions.get(appName)
if (!versions || versions.length < 2) {
throw new Error('没有可回滚的版本')
}
// 移除最新版本
versions.pop()
return versions[versions.length - 1]
}
}
const versionManager = new VersionManager()
// 注册版本
versionManager.register('app1', 'v1.0.0', 'https://cdn.example.com/app1/v1.0.0/')
versionManager.register('app1', 'v1.0.1', 'https://cdn.example.com/app1/v1.0.1/')
// 获取最新版本
const latest = versionManager.getLatest('app1')
// 回滚
const previous = versionManager.rollback('app1')
3.3 灰度发布
// 灰度发布策略
class GrayReleaseStrategy {
constructor() {
this.rules = []
}
// 添加灰度规则
addRule(rule) {
this.rules.push(rule)
}
// 判断用户是否命中灰度
shouldUseGrayVersion(userId) {
for (const rule of this.rules) {
if (rule.type === 'percentage') {
// 按百分比灰度
return this.hashUserId(userId) % 100 < rule.value
}
if (rule.type === 'whitelist') {
// 白名单用户
return rule.users.includes(userId)
}
if (rule.type === 'region') {
// 按地区灰度
const userRegion = this.getUserRegion(userId)
return rule.regions.includes(userRegion)
}
}
return false
}
hashUserId(userId) {
let hash = 0
for (let i = 0; i < userId.length; i++) {
hash = ((hash << 5) - hash) + userId.charCodeAt(i)
hash = hash & hash
}
return Math.abs(hash)
}
getUserRegion(userId) {
// 获取用户地区
return 'cn'
}
}
// 使用示例
const grayStrategy = new GrayReleaseStrategy()
// 10% 用户使用灰度版本
grayStrategy.addRule({
type: 'percentage',
value: 10
})
// 白名单用户
grayStrategy.addRule({
type: 'whitelist',
users: ['user1', 'user2', 'user3']
})
// 根据用户判断使用哪个版本
const userId = 'user123'
const version = grayStrategy.shouldUseGrayVersion(userId)
? 'v1.1.0-beta'
: 'v1.0.0'
console.log(`用户 ${userId} 使用版本: ${version}`)
3.4 回滚机制
// rollback.js
class RollbackManager {
constructor() {
this.deployHistory = []
this.maxHistory = 10
}
// 记录部署
recordDeploy(appName, version, entry) {
this.deployHistory.unshift({
appName,
version,
entry,
timestamp: Date.now()
})
// 只保留最近 10 次部署
if (this.deployHistory.length > this.maxHistory) {
this.deployHistory.pop()
}
// 持久化
this.save()
}
// 回滚
rollback(appName, steps = 1) {
const deployments = this.deployHistory.filter(d => d.appName === appName)
if (deployments.length < steps + 1) {
throw new Error(`没有足够的历史版本进行回滚(需要 ${steps + 1} 个,实际 ${deployments.length} 个)`)
}
// 返回目标版本
return deployments[steps]
}
// 获取部署历史
getHistory(appName) {
if (appName) {
return this.deployHistory.filter(d => d.appName === appName)
}
return this.deployHistory
}
// 保存到 localStorage
save() {
localStorage.setItem('deploy-history', JSON.stringify(this.deployHistory))
}
// 从 localStorage 加载
load() {
const data = localStorage.getItem('deploy-history')
if (data) {
this.deployHistory = JSON.parse(data)
}
}
}
const rollbackManager = new RollbackManager()
rollbackManager.load()
// 记录部署
rollbackManager.recordDeploy('app1', 'v1.0.1', 'https://cdn.example.com/app1/v1.0.1/')
// 回滚到上一版本
try {
const targetVersion = rollbackManager.rollback('app1', 1)
console.log('回滚到版本:', targetVersion)
// 重新加载应用
window.location.reload()
} catch (error) {
console.error('回滚失败:', error.message)
}
四、监控与调试
4.1 子应用加载监控
// monitor/app-loader.js
class AppLoadMonitor {
constructor() {
this.metrics = []
}
// 开始监控
start(appName) {
const startTime = performance.now()
return {
appName,
startTime,
end: () => {
const endTime = performance.now()
const duration = endTime - startTime
this.metrics.push({
appName,
startTime,
endTime,
duration,
timestamp: Date.now()
})
// 上报
this.report({
appName,
duration,
success: true
})
console.log(`[Monitor] ${appName} 加载耗时: ${duration.toFixed(2)}ms`)
},
error: (error) => {
const endTime = performance.now()
const duration = endTime - startTime
this.report({
appName,
duration,
success: false,
error: error.message
})
console.error(`[Monitor] ${appName} 加载失败:`, error)
}
}
}
// 上报数据
report(data) {
// 发送到监控服务
if (window.__MONITOR_API__) {
window.__MONITOR_API__.send('app-load', data)
}
// 或使用自己的上报接口
fetch('/api/monitor/app-load', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).catch(err => {
console.error('上报失败', err)
})
}
// 获取统计信息
getStats(appName) {
const appMetrics = this.metrics.filter(m => m.appName === appName)
if (appMetrics.length === 0) {
return null
}
const durations = appMetrics.map(m => m.duration)
return {
appName,
count: appMetrics.length,
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
min: Math.min(...durations),
max: Math.max(...durations)
}
}
}
const monitor = new AppLoadMonitor()
// 使用示例
registerMicroApps(apps, {
beforeLoad: [(app) => {
app.__loadMonitor__ = monitor.start(app.name)
}],
afterMount: [(app) => {
app.__loadMonitor__?.end()
}],
errorHandler: (error, app) => {
app.__loadMonitor__?.error(error)
}
})
4.2 错误捕获上报
// monitor/error-handler.js
class ErrorHandler {
constructor() {
this.errors = []
this.init()
}
init() {
// 捕获全局错误
window.addEventListener('error', (event) => {
this.captureError({
type: 'error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
})
})
// 捕获未处理的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
this.captureError({
type: 'unhandledrejection',
message: event.reason?.message || event.reason,
stack: event.reason?.stack
})
})
// 捕获 Vue 错误
if (window.app) {
window.app.config.errorHandler = (err, instance, info) => {
this.captureError({
type: 'vue-error',
message: err.message,
stack: err.stack,
component: info
})
}
}
}
captureError(error) {
// 添加额外信息
const enrichedError = {
...error,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
}
this.errors.push(enrichedError)
// 上报
this.report(enrichedError)
console.error('[ErrorHandler]', enrichedError)
}
report(error) {
// 发送到错误监控服务
fetch('/api/monitor/error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(error)
}).catch(err => {
console.error('错误上报失败', err)
})
}
// 获取错误列表
getErrors() {
return this.errors
}
// 清空错误
clear() {
this.errors = []
}
}
const errorHandler = new ErrorHandler()
window.__ERROR_HANDLER__ = errorHandler
4.3 性能监控
// monitor/performance.js
class PerformanceMonitor {
constructor() {
this.metrics = {}
}
// 记录性能指标
mark(name) {
performance.mark(name)
}
// 测量性能
measure(name, startMark, endMark) {
try {
performance.measure(name, startMark, endMark)
const entries = performance.getEntriesByName(name)
const latest = entries[entries.length - 1]
this.metrics[name] = latest.duration
// 上报
this.report({
name,
duration: latest.duration,
startTime: latest.startTime
})
return latest.duration
} catch (error) {
console.error('性能测量失败', error)
return 0
}
}
// 获取页面性能指标
getPageMetrics() {
const navigation = performance.getEntriesByType('navigation')[0]
if (!navigation) return null
return {
// DNS 查询时间
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
// TCP 连接时间
tcp: navigation.connectEnd - navigation.connectStart,
// 请求时间
request: navigation.responseStart - navigation.requestStart,
// 响应时间
response: navigation.responseEnd - navigation.responseStart,
// DOM 解析时间
domParse: navigation.domInteractive - navigation.responseEnd,
// 资源加载时间
resourceLoad: navigation.loadEventStart - navigation.domContentLoadedEventEnd,
// 总时间
total: navigation.loadEventEnd - navigation.fetchStart
}
}
// 获取资源加载情况
getResourceMetrics() {
const resources = performance.getEntriesByType('resource')
return resources.map(resource => ({
name: resource.name,
type: resource.initiatorType,
duration: resource.duration,
size: resource.transferSize
}))
}
// 上报
report(data) {
fetch('/api/monitor/performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).catch(err => {
console.error('性能数据上报失败', err)
})
}
}
const performanceMonitor = new PerformanceMonitor()
// 使用示例
performanceMonitor.mark('app-load-start')
// ... 加载应用
performanceMonitor.mark('app-load-end')
const duration = performanceMonitor.measure('app-load', 'app-load-start', 'app-load-end')
console.log('应用加载时间:', duration)
4.4 Chrome DevTools 调试技巧
// 调试工具
class MicroAppDebugger {
constructor() {
this.apps = new Map()
this.logs = []
// 挂载到全局
window.__MICRO_APP_DEBUGGER__ = this
}
// 注册应用
registerApp(name, app) {
this.apps.set(name, app)
this.log('info', `应用 ${name} 已注册`)
}
// 获取应用信息
getApp(name) {
return this.apps.get(name)
}
// 列出所有应用
listApps() {
console.table(
Array.from(this.apps.entries()).map(([name, app]) => ({
name,
status: app.status,
entry: app.entry
}))
)
}
// 打印应用状态
inspect(name) {
const app = this.apps.get(name)
if (!app) {
console.error(`应用 ${name} 不存在`)
return
}
console.group(`应用 ${name} 详情`)
console.log('状态:', app.status)
console.log('入口:', app.entry)
console.log('容器:', app.container)
console.log('Props:', app.props)
console.groupEnd()
}
// 日志记录
log(level, message, data) {
const log = {
level,
message,
data,
timestamp: new Date().toISOString()
}
this.logs.push(log)
console[level](`[MicroApp] ${message}`, data || '')
}
// 获取日志
getLogs(level) {
if (level) {
return this.logs.filter(log => log.level === level)
}
return this.logs
}
// 清空日志
clearLogs() {
this.logs = []
}
}
const debugger = new MicroAppDebugger()
// Chrome Console 使用
// __MICRO_APP_DEBUGGER__.listApps()
// __MICRO_APP_DEBUGGER__.inspect('app1')
// __MICRO_APP_DEBUGGER__.getLogs()
五、总结
微前端工程化是保证项目长期成功的关键,核心要点:
- 开发规范
- 统一的生命周期实现
- 环境判断和导出格式
- publicPath 配置
- 基座设计
- 统一 Layout 和导航
- 用户信息和权限管理
- 错误边界和容错处理
- 部署发布
- 独立部署策略
- 版本管理和灰度发布
- 回滚机制
- 监控调试
- 加载性能监控
- 错误捕获上报
- 调试工具
建立完善的工程化体系,可以大幅提升开发效率和系统稳定性。