"这个系统太庞大了,每次发布都提心吊胆..." 上个月的技术评审会上,我们团队正面临一个棘手的问题。一个运行了两年的企业级中后台系统,代码量超过 30 万行,构建时间长达 20 分钟,任何小改动都可能引发意想不到的问题。作为技术负责人,我决定是时候引入微前端架构了。
经过一个月的改造,我们成功将这个庞然大物拆分成多个独立应用,构建时间缩短到了 3 分钟,各个团队也能独立开发部署了。今天就来分享这次微前端改造的实战经验。
为什么选择微前端?
说实话,刚开始团队对微前端也有顾虑 - 会不会过度设计?性能会不会受影响?但当我们列出现有问题时,答案就很明显了:- // 原有的单体应用结构
- const LegacyApp = {
- modules: {
- crm: {
- size: '12MB JS + 2MB CSS',
- team: 'A团队',
- updateFrequency: '每周2次'
- },
- erp: {
- size: '15MB JS + 3MB CSS',
- team: 'B团队',
- updateFrequency: '每天1次'
- },
- dashboard: {
- size: '8MB JS + 1MB CSS',
- team: 'C团队',
- updateFrequency: '每月2次'
- }
- },
- problems: {
- buildTime: '20min+',
- deployment: '全量发布',
- teamCollaboration: '代码冲突频繁',
- maintenance: '难以局部更新'
- }
- }
复制代码 架构设计与实现
1. 基座应用
首先,我们需要一个轻量级的基座应用来管理子应用:- // 基座应用 - App Shell
- 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',
- container: '#crm-container'
- },
- {
- name: 'erp',
- entry: '//localhost:3002/main.js',
- container: '#erp-container'
- },
- {
- name: 'dashboard',
- entry: '//localhost:3003/main.js',
- container: '#dashboard-container'
- }
- ]
- // 注册所有子应用
- microApps.forEach(app => registerMicroApp(app.name, app.entry))
- // 启动微前端框架
- start()
复制代码 2. 子应用改造
每个子应用需要暴露生命周期钩子:- // 子应用入口
- import React from 'react'
- import ReactDOM from 'react-dom'
- import App from './App'
- import { createStore } from './store'
- // 导出生命周期钩子
- export async function bootstrap() {
- console.log('CRM 应用启动中...')
- }
- export async function mount(props) {
- const { container, globalStore } = props
- const store = createStore(globalStore)
- ReactDOM.render(
- <Provider store={store}>
-
- </Provider>,
- container
- )
- }
- export async function unmount(props) {
- const { container } = props
- ReactDOM.unmountComponentAtNode(container)
- }
复制代码 3. 通信机制
子应用间的通信是个关键问题,我们实现了一个事件总线:- // 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(`Error in event ${event}:`, error)
- }
- })
- }
- }
- export const eventBus = new EventBus()
- // 使用示例
- // CRM 子应用
- eventBus.emit('orderCreated', { orderId: '123' })
- // ERP 子应用
- eventBus.on('orderCreated', data => {
- updateInventory(data.orderId)
- })
复制代码 4. 样式隔离
为了避免样式冲突,我们采用了 CSS Modules 和动态 CSS 前缀:- // 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="crm"]'
- })
- ]
- }
- }
- ]
- }
- ]
- }
- }
复制代码 性能优化
微前端虽然解决了很多问题,但也带来了新的挑战,比如首屏加载性能。我们通过以下方式进行优化:
- // 基于路由预测用户行为
- 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)
- })
- }
复制代码- // webpack.config.js
- module.exports = {
- externals: {
- react: 'React',
- 'react-dom': 'ReactDOM',
- antd: 'antd'
- },
- // 使用 CDN 加载共享依赖
- scripts: ['https://unpkg.com/react@17/umd/react.production.min.js', 'https://unpkg.com/react-dom@17/umd/react-dom.production.min.js', 'https://unpkg.com/antd@4/dist/antd.min.js']
- }
复制代码 实践心得
这次微前端改造让我深刻体会到:
- 架构改造要循序渐进,先从边界清晰的模块开始
- 子应用拆分要基于业务边界,而不是技术边界
- 通信机制要简单可靠,避免复杂的状态同步
- 持续关注性能指标,及时发现和解决问题
最让我欣慰的是,改造后团队的开发效率明显提升,发布也更加灵活可控。正如那句话说的:"合久必分,分久必合。"在前端架构的演进中,找到当下最合适的平衡点才是关键。
写在最后
微前端不是银弹,它更像是一把双刃剑 - 使用得当可以解决很多问题,但也可能引入新的复杂性。关键是要根据团队和业务的实际情况,做出合适的选择。
有什么问题欢迎在评论区讨论,我们一起探讨微前端实践的更多可能!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多架构实战经验~
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |