Web Components 微前端实现与应用
Web Components 微前端实现与应用
1. 核心架构设计
1.1 微前端架构模式
// 微前端核心接口定义
interface MicroFrontendConfig {
name: string;
entry: string; // 应用入口
container: string; // 挂载容器
activeRule: string; // 激活规则
props?: Record<string, any>; // 传递的属性
}
interface WebComponentConfig {
tagName: string; // 自定义元素标签
shadowDom?: boolean; // 是否使用 Shadow DOM
styleSheets?: string[]; // 样式表
}
class MicroFrontendApp {
private config: MicroFrontendConfig;
private componentConfig: WebComponentConfig;
private isMounted: boolean = false;
constructor(config: MicroFrontendConfig, componentConfig: WebComponentConfig) {
this.config = config;
this.componentConfig = componentConfig;
}
async load(): Promise<void> {
// 动态加载应用资源
}
mount(container: HTMLElement): void {
// 挂载应用到容器
}
unmount(): void {
// 卸载应用
}
}
2. Web Components 基础实现
2.1 基础 Web Component 类
// 基础微前端 Web Component
abstract class MicroFrontendComponent extends HTMLElement {
protected shadow: ShadowRoot;
protected appContainer: HTMLElement;
protected styles: string;
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.styles = this.getStyles();
this.appContainer = document.createElement('div');
this.appContainer.className = 'micro-app-container';
}
// 生命周期方法
connectedCallback() {
this.render();
this.mountApp();
}
disconnectedCallback() {
this.unmountApp();
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (oldValue !== newValue) {
this.onAttributeChange(name, newValue);
}
}
// 抽象方法
protected abstract getStyles(): string;
protected abstract mountApp(): Promise<void>;
protected abstract unmountApp(): void;
protected abstract onAttributeChange(name: string, value: string): void;
// 渲染基础结构
protected render() {
this.shadow.innerHTML = `
<style>${this.styles}</style>
<div class="micro-app">
<div class="loading" id="loading">加载中...</div>
${this.appContainer.outerHTML}
</div>
`;
this.appContainer = this.shadow.getElementById('app-container') as HTMLElement;
}
// 显示/隐藏加载状态
protected showLoading() {
const loading = this.shadow.getElementById('loading');
if (loading) loading.style.display = 'block';
}
protected hideLoading() {
const loading = this.shadow.getElementById('loading');
if (loading) loading.style.display = 'none';
}
}
2.2 具体的微前端组件实现
// 具体的微前端应用组件
class UserManagementApp extends MicroFrontendComponent {
private scriptElement?: HTMLScriptElement;
private styleElement?: HTMLLinkElement;
private appInstance: any;
static get observedAttributes() {
return ['api-url', 'theme', 'locale'];
}
protected getStyles(): string {
return `
:host {
display: block;
width: 100%;
height: 100%;
}
.micro-app {
width: 100%;
height: 100%;
font-family: inherit;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
font-size: 16px;
color: #666;
}
.micro-app-container {
width: 100%;
height: 100%;
}
`;
}
protected async mountApp(): Promise<void> {
try {
this.showLoading();
// 动态加载应用资源
await this.loadResources();
// 初始化应用
await this.initializeApp();
this.hideLoading();
} catch (error) {
console.error('Failed to mount user management app:', error);
this.showError('应用加载失败');
}
}
protected unmountApp(): void {
// 清理资源
if (this.appInstance && typeof this.appInstance.unmount === 'function') {
this.appInstance.unmount();
}
// 移除动态添加的脚本和样式
if (this.scriptElement) {
document.head.removeChild(this.scriptElement);
}
if (this.styleElement) {
document.head.removeChild(this.styleElement);
}
// 清理容器
this.appContainer.innerHTML = '';
}
protected onAttributeChange(name: string, value: string): void {
switch (name) {
case 'api-url':
this.updateApiUrl(value);
break;
case 'theme':
this.updateTheme(value);
break;
case 'locale':
this.updateLocale(value);
break;
}
}
private async loadResources(): Promise<void> {
const baseUrl = this.getAttribute('base-url') || 'https://cdn.example.com/user-app';
// 加载样式
this.styleElement = document.createElement('link');
this.styleElement.rel = 'stylesheet';
this.styleElement.href = `${baseUrl}/styles.css`;
document.head.appendChild(this.styleElement);
// 加载脚本
this.scriptElement = document.createElement('script');
this.scriptElement.src = `${baseUrl}/app.js`;
this.scriptElement.type = 'module';
await new Promise((resolve, reject) => {
this.scriptElement!.onload = resolve;
this.scriptElement!.onerror = reject;
document.head.appendChild(this.scriptElement!);
});
}
private async initializeApp(): Promise<void> {
// 从全局对象获取应用工厂函数
const appFactory = (window as any).UserManagementApp;
if (!appFactory) {
throw new Error('UserManagementApp not found');
}
// 获取配置属性
const config = {
apiUrl: this.getAttribute('api-url') || '/api',
theme: this.getAttribute('theme') || 'light',
locale: this.getAttribute('locale') || 'zh-CN',
container: this.appContainer,
onEvent: this.handleAppEvent.bind(this)
};
// 初始化应用
this.appInstance = appFactory(config);
if (this.appInstance && typeof this.appInstance.mount === 'function') {
await this.appInstance.mount();
}
}
private handleAppEvent(event: any) {
// 处理应用内部事件
this.dispatchEvent(new CustomEvent('app-event', {
detail: event,
bubbles: true,
composed: true
}));
}
private updateApiUrl(url: string) {
if (this.appInstance && typeof this.appInstance.updateApiUrl === 'function') {
this.appInstance.updateApiUrl(url);
}
}
private updateTheme(theme: string) {
if (this.appInstance && typeof this.appInstance.updateTheme === 'function') {
this.appInstance.updateTheme(theme);
}
}
private updateLocale(locale: string) {
if (this.appInstance && typeof this.appInstance.updateLocale === 'function') {
this.appInstance.updateLocale(locale);
}
}
private showError(message: string) {
this.appContainer.innerHTML = `
<div class="error-container">
<h3>应用加载失败</h3>
<p>${message}</p>
<button onclick="location.reload()">重试</button>
</div>
`;
}
}
// 注册自定义元素
customElements.define('user-management-app', UserManagementApp);
3. 微前端路由系统
3.1 基于 Web Components 的路由器
class MicroFrontendRouter {
private apps: Map<string, MicroFrontendApp> = new Map();
private currentApp: string | null = null;
private routeConfig: RouteConfig[] = [];
constructor(private container: HTMLElement) {
this.initRouter();
}
// 注册微前端应用
registerApp(config: MicroFrontendConfig): void {
const app = new MicroFrontendApp(config);
this.apps.set(config.name, app);
this.routeConfig.push({
name: config.name,
activeRule: config.activeRule
});
}
// 启动路由
start(): void {
this.navigateTo(window.location.pathname);
window.addEventListener('popstate', () => {
this.navigateTo(window.location.pathname);
});
}
// 导航到指定路径
navigateTo(path: string): void {
const appConfig = this.findAppByPath(path);
if (!appConfig) {
this.showNotFound();
return;
}
if (this.currentApp === appConfig.name) {
return; // 已经是当前应用
}
// 卸载当前应用
if (this.currentApp) {
const currentApp = this.apps.get(this.currentApp);
currentApp?.unmount();
}
// 加载新应用
const nextApp = this.apps.get(appConfig.name);
if (nextApp) {
this.currentApp = appConfig.name;
nextApp.mount(this.container);
// 更新浏览器历史
if (window.location.pathname !== path) {
window.history.pushState(null, '', path);
}
}
}
private findAppByPath(path: string): RouteConfig | null {
return this.routeConfig.find(route =>
typeof route.activeRule === 'function'
? route.activeRule(path)
: path.startsWith(route.activeRule as string)
) || null;
}
private showNotFound(): void {
this.container.innerHTML = `
<div class="not-found">
<h1>404 - 页面未找到</h1>
<p>请求的路径 ${window.location.pathname} 不存在</p>
</div>
`;
}
private initRouter(): void {
// 拦截链接点击事件
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const link = target.closest('a[href]');
if (link && this.isInternalLink(link as HTMLAnchorElement)) {
e.preventDefault();
const href = (link as HTMLAnchorElement).href;
const path = new URL(href).pathname;
this.navigateTo(path);
}
});
}
private isInternalLink(link: HTMLAnchorElement): boolean {
return link.origin === window.location.origin;
}
}
interface RouteConfig {
name: string;
activeRule: string | ((path: string) => boolean);
}
3.2 应用间通信系统
// 微前端事件总线
class MicroFrontendEventBus {
private listeners: Map<string, Function[]> = new Map();
// 发布事件
emit(event: string, data?: any): void {
const eventListeners = this.listeners.get(event) || [];
eventListeners.forEach(listener => {
try {
listener(data);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
// 同时派发到全局
window.dispatchEvent(new CustomEvent(`micro-frontend:${event}`, {
detail: data
}));
}
// 订阅事件
on(event: string, listener: Function): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(listener);
// 返回取消订阅函数
return () => this.off(event, listener);
}
// 取消订阅
off(event: string, listener: Function): void {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
const index = eventListeners.indexOf(listener);
if (index > -1) {
eventListeners.splice(index, 1);
}
}
}
// 一次性订阅
once(event: string, listener: Function): void {
const onceListener = (data: any) => {
listener(data);
this.off(event, onceListener);
};
this.on(event, onceListener);
}
}
// 全局状态管理
class GlobalStateManager {
private state: Record<string, any> = {};
private subscribers: Map<string, Function[]> = new Map();
setState(key: string, value: any): void {
const oldValue = this.state[key];
this.state[key] = value;
// 通知订阅者
const keySubscribers = this.subscribers.get(key) || [];
keySubscribers.forEach(subscriber => {
try {
subscriber(value, oldValue);
} catch (error) {
console.error(`Error in state subscriber for ${key}:`, error);
}
});
}
getState<T = any>(key: string): T | undefined {
return this.state[key];
}
subscribe(key: string, subscriber: (newValue: any, oldValue: any) => void): () => void {
if (!this.subscribers.has(key)) {
this.subscribers.set(key, []);
}
this.subscribers.get(key)!.push(subscriber);
return () => this.unsubscribe(key, subscriber);
}
unsubscribe(key: string, subscriber: Function): void {
const keySubscribers = this.subscribers.get(key);
if (keySubscribers) {
const index = keySubscribers.indexOf(subscriber);
if (index > -1) {
keySubscribers.splice(index, 1);
}
}
}
}
// 全局单例
export const eventBus = new MicroFrontendEventBus();
export const globalState = new GlobalStateManager();
4. 完整的微前端应用示例
4.1 主应用 (Shell App)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>微前端主应用</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.header {
background: #f5f5f5;
padding: 1rem;
border-bottom: 1px solid #ddd;
}
.nav {
display: flex;
gap: 1rem;
}
.nav a {
text-decoration: none;
color: #333;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.nav a:hover {
background: #e9e9e9;
}
.nav a.active {
background: #007acc;
color: white;
}
.main-container {
padding: 1rem;
height: calc(100vh - 80px);
}
</style>
</head>
<body>
<div class="header">
<h1>微前端平台</h1>
<div class="nav">
<a href="/" data-route="/">首页</a>
<a href="/user-management" data-route="/user-management">用户管理</a>
<a href="/product-management" data-route="/product-management">产品管理</a>
<a href="/order-management" data-route="/order-management">订单管理</a>
</div>
</div>
<div class="main-container" id="app-container">
<!-- 微前端应用将在这里渲染 -->
</div>
<script type="module">
import { MicroFrontendRouter } from './micro-frontend-router.js';
// 初始化路由
const router = new MicroFrontendRouter(
document.getElementById('app-container')
);
// 注册微前端应用
router.registerApp({
name: 'home',
entry: '/apps/home/app.js',
container: '#app-container',
activeRule: '/'
});
router.registerApp({
name: 'user-management',
entry: '/apps/user-management/app.js',
container: '#app-container',
activeRule: '/user-management'
});
router.registerApp({
name: 'product-management',
entry: '/apps/product-management/app.js',
container: '#app-container',
activeRule: '/product-management'
});
router.registerApp({
name: 'order-management',
entry: '/apps/order-management/app.js',
container: '#app-container',
activeRule: '/order-management'
});
// 启动路由
router.start();
// 导航高亮
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const link = target.closest('a[data-route]');
if (link) {
document.querySelectorAll('a[data-route]').forEach(a => {
a.classList.remove('active');
});
link.classList.add('active');
}
});
</script>
</body>
</html>
4.2 用户管理微应用
// apps/user-management/app.js
class UserManagementApp {
constructor(config) {
this.config = config;
this.container = config.container;
this.apiUrl = config.apiUrl;
this.eventHandler = config.onEvent;
}
async mount() {
console.log('Mounting User Management App');
// 渲染应用界面
this.render();
// 加载用户数据
await this.loadUsers();
// 绑定事件
this.bindEvents();
}
unmount() {
console.log('Unmounting User Management App');
this.unbindEvents();
this.container.innerHTML = '';
}
render() {
this.container.innerHTML = `
<div class="user-management-app">
<h2>用户管理系统</h2>
<div class="toolbar">
<button id="add-user">添加用户</button>
<input type="text" id="search" placeholder="搜索用户...">
</div>
<div class="user-list" id="user-list">
<div class="loading">加载中...</div>
</div>
</div>
`;
}
async loadUsers() {
try {
const response = await fetch(`${this.apiUrl}/users`);
const users = await response.json();
const userList = this.container.querySelector('#user-list');
userList.innerHTML = users.map(user => `
<div class="user-item" data-user-id="${user.id}">
<span class="user-name">${user.name}</span>
<span class="user-email">${user.email}</span>
<button class="edit-btn">编辑</button>
<button class="delete-btn">删除</button>
</div>
`).join('');
} catch (error) {
console.error('Failed to load users:', error);
this.container.querySelector('#user-list').innerHTML =
'<div class="error">加载用户失败</div>';
}
}
bindEvents() {
this.container.addEventListener('click', this.handleClick.bind(this));
this.container.addEventListener('input', this.handleSearch.bind(this));
}
unbindEvents() {
this.container.removeEventListener('click', this.handleClick.bind(this));
this.container.removeEventListener('input', this.handleSearch.bind(this));
}
handleClick(event) {
const target = event.target;
if (target.id === 'add-user') {
this.eventHandler({ type: 'ADD_USER' });
} else if (target.classList.contains('edit-btn')) {
const userId = target.closest('.user-item').dataset.userId;
this.eventHandler({ type: 'EDIT_USER', userId });
} else if (target.classList.contains('delete-btn')) {
const userId = target.closest('.user-item').dataset.userId;
this.deleteUser(userId);
}
}
handleSearch(event) {
if (event.target.id === 'search') {
const searchTerm = event.target.value;
// 实现搜索逻辑
this.eventHandler({ type: 'SEARCH_USERS', searchTerm });
}
}
async deleteUser(userId) {
if (confirm('确定要删除这个用户吗?')) {
try {
await fetch(`${this.apiUrl}/users/${userId}`, {
method: 'DELETE'
});
await this.loadUsers(); // 重新加载列表
this.eventHandler({ type: 'USER_DELETED', userId });
} catch (error) {
console.error('Failed to delete user:', error);
alert('删除用户失败');
}
}
}
updateApiUrl(newUrl) {
this.apiUrl = newUrl;
this.loadUsers(); // 重新加载数据
}
updateTheme(theme) {
this.container.setAttribute('data-theme', theme);
}
updateLocale(locale) {
// 更新本地化
console.log('Update locale to:', locale);
}
}
// 暴露给全局,供 Web Component 调用
window.UserManagementApp = (config) => new UserManagementApp(config);
4.3 样式文件
/* apps/user-management/styles.css */
.user-management-app {
padding: 20px;
font-family: Arial, sans-serif;
}
.user-management-app[data-theme="dark"] {
background: #333;
color: white;
}
.user-management-app .toolbar {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.user-management-app button {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
}
.user-management-app button:hover {
background: #f5f5f5;
}
.user-management-app input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-list {
border: 1px solid #eee;
border-radius: 4px;
}
.user-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
gap: 10px;
}
.user-item:last-child {
border-bottom: none;
}
.user-name {
font-weight: bold;
min-width: 100px;
}
.user-email {
color: #666;
flex: 1;
}
.loading, .error {
text-align: center;
padding: 40px;
color: #666;
}
.error {
color: #d32f2f;
}
5. 高级特性
5.1 应用预加载
class AppPreloader {
private preloadedApps: Map<string, boolean> = new Map();
// 预加载应用资源
async preloadApp(appName: string, entry: string): Promise<void> {
if (this.preloadedApps.has(appName)) {
return;
}
try {
// 预加载脚本
const script = document.createElement('script');
script.src = entry;
script.type = 'module';
script.async = true;
await new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
this.preloadedApps.set(appName, true);
console.log(`Preloaded app: ${appName}`);
} catch (error) {
console.error(`Failed to preload app ${appName}:`, error);
}
}
// 基于用户行为预测预加载
predictivePreload(currentPath: string): void {
const likelyNextApps = this.predictNextApps(currentPath);
likelyNextApps.forEach(app => {
this.preloadApp(app.name, app.entry);
});
}
private predictNextApps(currentPath: string): Array<{name: string, entry: string}> {
// 基于当前路径预测用户可能访问的下一个应用
const predictions = [];
if (currentPath === '/') {
predictions.push(
{ name: 'user-management', entry: '/apps/user-management/app.js' }
);
}
return predictions;
}
}
5.2 性能监控
class PerformanceMonitor {
private metrics: Map<string, number> = new Map();
startMeasure(appName: string, metric: string): void {
const key = `${appName}:${metric}`;
this.metrics.set(key, performance.now());
}
endMeasure(appName: string, metric: string): number {
const key = `${appName}:${metric}`;
const startTime = this.metrics.get(key);
if (startTime) {
const duration = performance.now() - startTime;
this.metrics.delete(key);
// 上报性能数据
this.reportMetric(appName, metric, duration);
return duration;
}
return 0;
}
private reportMetric(appName: string, metric: string, value: number): void {
// 上报到监控系统
console.log(`[Performance] ${appName} - ${metric}: ${value.toFixed(2)}ms`);
// 可以发送到 APM 系统
if (navigator.sendBeacon) {
const data = {
app: appName,
metric,
value,
timestamp: Date.now()
};
navigator.sendBeacon('/api/performance', JSON.stringify(data));
}
}
// 监控应用加载性能
monitorAppLoad(appName: string, loadPromise: Promise<any>): void {
this.startMeasure(appName, 'app-load');
loadPromise
.then(() => {
const loadTime = this.endMeasure(appName, 'app-load');
console.log(`App ${appName} loaded in ${loadTime}ms`);
})
.catch(error => {
console.error(`App ${appName} failed to load:`, error);
});
}
}
这种基于 Web Components 的微前端架构提供了:
- 技术栈无关性:每个微应用可以使用不同的技术栈
- 样式隔离:通过 Shadow DOM 实现样式隔离
- 独立部署:每个微应用可以独立开发、测试和部署
- 运行时集成:动态加载和卸载应用
- 通信机制:通过事件总线和全局状态实现应用间通信
- 性能优化:支持预加载和懒加载
这种架构特别适合大型复杂应用的模块化开发和维护。
挣钱养家

浙公网安备 33010602011771号