"这个系统已经无法维护了..."周五的架构评审会上,我盯着屏幕上那张错综复杂的依赖关系图发愁。作为一个运行了三年的企业级中后台系统,代码量已经超过 50 万行,构建时间长达 40 分钟,任何修改都可能引发连锁反应。
更让人头疼的是,随着业务的快速发展,不同业务线之间的耦合越来越严重。一个小小的样式修改都可能影响到其他模块的展示。经过团队讨论,我们决定采用微前端架构对系统进行拆分重构。
现状分析
首先梳理了系统目前面临的主要问题:
- 构建部署效率低下
- 团队协作成本高
- 技术栈难以升级
- 代码复用困难
- 线上问题难以隔离
就像一座不断扩建的大楼,没有合理的分区和规划,最终变得杂乱无章。我们需要把这座大楼重新规划成一个个独立又互联的空间。
架构设计
经过调研和验证,我们选择了基于 single-spa 的微前端方案:
// 基座应用
import { registerApplication, start } from 'single-spa'
// 注册子应用
const registerMicroApp = (name: string, entry: string) => {
registerApplication({
name,
app: async () => {
// 动态加载子应用
const module = await System.import(entry)
return module.default
},
activeWhen: location => {
// 基于路由匹配激活子应用
return location.pathname.startsWith(`/${name}`)
}
})
}
// 子应用配置
const microApps = [
{
name: 'crm',
entry: '//localhost:3001/main.js',
activeRule: '/crm'
},
{
name: 'erp',
entry: '//localhost:3002/main.js',
activeRule: '/erp'
},
{
name: 'dashboard',
entry: '//localhost:3003/main.js',
activeRule: '/dashboard'
}
]
// 注册所有子应用
microApps.forEach(app => registerMicroApp(app.name, app.entry))
// 启动微前端框架
start()
子应用改造
每个子应用需要暴露生命周期钩子:
// 子应用入口
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from './store'
// 导出生命周期钩子
export async function bootstrap() {
console.log('应用启动中...')
}
export async function mount(props) {
const { container, globalStore } = props
const store = createStore(globalStore)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
container
)
}
export async function unmount(props) {
const { container } = props
ReactDOM.unmountComponentAtNode(container)
}
通信方案
子应用间的通信是一个关键问题,我们实现了一个事件总线:
// utils/eventBus.ts
class EventBus {
private events = new Map<string, Function[]>()
// 订阅事件
on(event: string, callback: Function) {
if (!this.events.has(event)) {
this.events.set(event, [])
}
this.events.get(event).push(callback)
// 返回取消订阅函数
return () => {
const callbacks = this.events.get(event)
const index = callbacks.indexOf(callback)
callbacks.splice(index, 1)
}
}
// 发布事件
emit(event: string, data?: any) {
if (!this.events.has(event)) return
this.events.get(event).forEach(callback => {
try {
callback(data)
} catch (error) {
console.error(`事件处理错误: ${event}`, error)
}
})
}
}
export const eventBus = new EventBus()
样式隔离
为了避免样式冲突,我们采用了 CSS Modules 和动态前缀:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
plugins: [
require('postcss-prefix-selector')({
prefix: `[data-app="${process.env.APP_NAME}"]`
})
]
}
}
]
}
]
}
}
性能优化
微前端架构下的性能优化主要从这几个方面入手:
// 预加载策略
const prefetchApps = async () => {
const nextPossibleApps = predictNextApps()
nextPossibleApps.forEach(app => {
const script = document.createElement('link')
script.rel = 'prefetch'
script.href = app.entry
document.head.appendChild(script)
})
}
// 共享依赖
const sharedDependencies = {
react: {
singleton: true,
requiredVersion: '^17.0.0'
},
'react-dom': {
singleton: true,
requiredVersion: '^17.0.0'
},
'react-router-dom': {
singleton: true,
requiredVersion: '^5.2.0'
}
}
实践效果
经过三个月的改造,我们取得了显著的成效:
- 构建时间从40 分钟减少到 5 分钟
- 各团队可以独立开发部署
- 线上问题可以快速定位和修复
- 新技术栈的尝试变得容易
最让我印象深刻的是一位同事说:"现在终于可以专注于业务开发,不用担心影响到其他团队了。"
经验总结
微前端架构就像城市规划,需要统筹兼顾又要保持灵活。我们的经验是:
合理分层 - 基座应用要足够稳定清晰边界 - 子应用之间要解耦统一规范 - 公共依赖要严格管理持续优化 - 性能问题要重点关注
写在最后
微前端不是银弹,它更像是一把双刃剑。使用得当可以大幅提升开发效率,但也会带来一定的复杂性。关键是要在架构设计时充分权衡,在实施过程中严格把控。
有什么问题欢迎在评论区讨论,让我们一起探讨微前端架构的最佳实践!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~