"這個系統太龐大了,每次釋出都提心吊膽..." 上個月的技術評審會上,我們團隊正面臨一個棘手的問題。一個執行了兩年的企業級中後臺系統,程式碼量超過 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}>
<App />
</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']
}
實踐心得
這次微前端改造讓我深刻體會到:
- 架構改造要循序漸進,先從邊界清晰的模組開始
- 子應用拆分要基於業務邊界,而不是技術邊界
- 通訊機制要簡單可靠,避免複雜的狀態同步
- 持續關注效能指標,及時發現和解決問題
最讓我欣慰的是,改造後團隊的開發效率明顯提升,釋出也更加靈活可控。正如那句話說的:"合久必分,分久必合。"在前端架構的演進中,找到當下最合適的平衡點才是關鍵。
寫在最後
微前端不是銀彈,它更像是一把雙刃劍 - 使用得當可以解決很多問題,但也可能引入新的複雜性。關鍵是要根據團隊和業務的實際情況,做出合適的選擇。
有什麼問題歡迎在評論區討論,我們一起探討微前端實踐的更多可能!
如果覺得有幫助,別忘了點贊關注,我會繼續分享更多架構實戰經驗~