一、什么是微前端
微前端是一种类似于微服务的架构理念,它将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。
1. 核心价值
- 技术栈无关
- 独立开发部署
- 增量升级
- 团队自治
二、实现方案
1. 基于路由分发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class MicroRouter { constructor() { this.apps = new Map() this.currentApp = null this.init() } init() { window.addEventListener('popstate', () => { this.handleRoute(window.location.pathname) }) } register(path, app) { this.apps.set(path, app) } handleRoute(path) { const app = this.apps.get(path) if (app) { if (this.currentApp) { this.currentApp.unmount() } this.currentApp = app app.mount() } } }
const router = new MicroRouter()
router.register('/app1', { mount: () => { }, unmount: () => { } })
|
2. 基于 Web Components
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class MicroApp extends HTMLElement { constructor() { super() this.shadow = this.attachShadow({ mode: 'open' }) } static get observedAttributes() { return ['name', 'url'] } async connectedCallback() { const name = this.getAttribute('name') const url = this.getAttribute('url') try { const module = await this.loadModule(url) this.mountApp(module) } catch (error) { console.error(`Failed to load micro app ${name}:`, error) } } async loadModule(url) { const response = await fetch(url) const code = await response.text() return new Function('exports', code) } mountApp(module) { const exports = {} module(exports) if (exports.render) { const container = document.createElement('div') exports.render(container) this.shadow.appendChild(container) } } disconnectedCallback() { } }
customElements.define('micro-app', MicroApp)
|
3. 基于 Module Federation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'container', remotes: { app1: 'app1@http://localhost:3001/remoteEntry.js', app2: 'app2@http://localhost:3002/remoteEntry.js' }, shared: ['react', 'react-dom'] }) ] }
const App1 = React.lazy(() => import('app1/App')) const App2 = React.lazy(() => import('app2/App'))
function Container() { return ( <div> <React.Suspense fallback="Loading App1"> <App1 /> </React.Suspense> <React.Suspense fallback="Loading App2"> <App2 /> </React.Suspense> </div> ) }
|
三、通信机制
1. 事件总线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class EventBus { constructor() { this.events = new Map() } on(event, callback) { if (!this.events.has(event)) { this.events.set(event, new Set()) } this.events.get(event).add(callback) } off(event, callback) { if (this.events.has(event)) { this.events.get(event).delete(callback) } } emit(event, data) { if (this.events.has(event)) { for (const callback of this.events.get(event)) { callback(data) } } } }
const bus = new EventBus()
bus.on('data-update', (data) => { console.log('App1 received:', data) })
bus.emit('data-update', { value: 123 })
|
2. 状态共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class SharedState { constructor() { this.state = {} this.listeners = new Set() } setState(path, value) { const oldValue = this.getState(path) if (oldValue !== value) { this.updateState(path, value) this.notifyListeners(path, value, oldValue) } } getState(path) { return path.split('.').reduce((obj, key) => obj?.[key], this.state) } updateState(path, value) { const keys = path.split('.') const lastKey = keys.pop() const target = keys.reduce((obj, key) => { if (!obj[key]) obj[key] = {} return obj[key] }, this.state) target[lastKey] = value } subscribe(callback) { this.listeners.add(callback) return () => this.listeners.delete(callback) } notifyListeners(path, value, oldValue) { for (const listener of this.listeners) { listener(path, value, oldValue) } } }
|
四、部署策略
1. 独立部署
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class DeploymentManager { constructor() { this.apps = new Map() this.versions = new Map() } async deploy(appName, version, assets) { await this.uploadAssets(appName, version, assets) this.versions.set(appName, version) this.notifyVersionUpdate(appName, version) } async uploadAssets(appName, version, assets) { const cdn = new CDNClient() const urls = await cdn.upload(`${appName}/${version}`, assets) this.apps.set(appName, urls) } notifyVersionUpdate(appName, version) { window.dispatchEvent( new CustomEvent('app-version-update', { detail: { appName, version } }) ) } }
|
2. 灰度发布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class GrayRelease { constructor() { this.rules = new Map() } addRule(appName, rule) { this.rules.set(appName, rule) } shouldUseNewVersion(appName, context) { const rule = this.rules.get(appName) if (!rule) return false return this.evaluateRule(rule, context) } evaluateRule(rule, context) { if (rule.percentage) { const random = Math.random() * 100 if (random > rule.percentage) return false } if (rule.userGroups && !rule.userGroups.includes(context.userGroup)) { return false } if (rule.regions && !rule.regions.includes(context.region)) { return false } return true } }
|
五、性能优化
1. 资源加载优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class ResourceLoader { constructor() { this.cache = new Map() this.loading = new Map() } async load(url) { if (this.cache.has(url)) { return this.cache.get(url) } if (this.loading.has(url)) { return this.loading.get(url) } const promise = this.loadResource(url) this.loading.set(url, promise) try { const resource = await promise this.cache.set(url, resource) return resource } finally { this.loading.delete(url) } } async loadResource(url) { const response = await fetch(url) if (!response.ok) { throw new Error(`Failed to load ${url}`) } return response.text() } }
|
2. 预加载策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class PreloadManager { constructor() { this.loader = new ResourceLoader() this.rules = new Map() } addRule(path, resources) { this.rules.set(path, resources) } handleRouteChange(path) { const resources = this.rules.get(path) if (resources) { this.preloadResources(resources) } } preloadResources(resources) { for (const url of resources) { const link = document.createElement('link') link.rel = 'prefetch' link.href = url document.head.appendChild(link) } } }
|
六、监控与日志
1. 性能监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class PerformanceMonitor { constructor() { this.metrics = {} } trackAppLoad(appName) { const startTime = performance.now() return { end: () => { const duration = performance.now() - startTime this.recordMetric(appName, 'load', duration) } } } recordMetric(appName, metric, value) { if (!this.metrics[appName]) { this.metrics[appName] = {} } if (!this.metrics[appName][metric]) { this.metrics[appName][metric] = [] } this.metrics[appName][metric].push({ value, timestamp: Date.now() }) } getMetrics(appName) { return this.metrics[appName] || {} } }
|
2. 错误监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class ErrorTracker { constructor() { this.errors = [] this.init() } init() { window.addEventListener('error', (event) => { this.trackError({ type: 'runtime', error: event.error, source: event.filename, line: event.lineno, column: event.colno }) }) window.addEventListener('unhandledrejection', (event) => { this.trackError({ type: 'promise', error: event.reason }) }) } trackError(error) { this.errors.push({ ...error, timestamp: Date.now() }) this.reportError(error) } async reportError(error) { try { await fetch('/api/errors', { method: 'POST', body: JSON.stringify(error) }) } catch (e) { console.error('Failed to report error:', e) } } }
|
参考文献