每天一个高级前端知识 - Day 23
今日主题:前端架构演进 - 从单体到微前端,再到岛屿架构
核心概念:架构的演进是为了解决规模带来的复杂度
随着应用规模和团队规模的扩大,前端架构经历了从单体应用 → 微前端 → 岛屿架构的演进。2024-2025年,岛屿架构正在成为大型内容型网站的新选择。
🔬 架构演进路线图
第一代:单体应用 (Monolith)
├── 优势:简单直接,开发初期效率高
├── 劣势:代码耦合,团队协作困难,性能瓶颈
└── 代表:传统 jQuery/React SPA
第二代:微前端 (Micro-Frontend)
├── 优势:技术栈无关,独立部署,团队自治
├── 劣势:复杂度高,重复依赖,样式隔离困难
└── 代表:qiankun、Module Federation、无界
第三代:岛屿架构 (Islands Architecture) ← 当前趋势
├── 优势:性能极佳,渐进式增强,SEO友好
├── 劣势:需要框架支持,学习曲线
└── 代表:Astro、Fresh、Next.js Partial Prerendering
🏝️ 岛屿架构深度解析
核心思想:在静态HTML中注入独立的交互"岛屿",每个岛屿都是独立的客户端组件。
┌─────────────────────────────────────────────────────────┐
│ 静态 HTML │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 🏝️ 评论区岛屿 (交互式) │ │
│ │ • React 组件 • 独立加载 • 懒执行 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 静态内容(Markdown/文本) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 🏝️ 购物车岛屿 (交互式) │ │
│ │ • Vue 组件 • 独立状态 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 🏝️ 订阅表单岛屿 (交互式) │ │
│ │ • Preact 组件 • 极轻量 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
⚡ Astro 岛屿架构实战
---
// ============ Astro 组件(默认零JS) ============
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import Header from '../components/Header.astro';
import ProductList from '../components/ProductList.astro';
import Cart from '../components/Cart.tsx';
import Comments from '../components/Comments.vue';
import Newsletter from '../components/Newsletter.svelte';
// --- 静态数据(构建时或请求时获取)---
const products = await fetchProducts();
const comments = await fetchComments();
---
<Layout title="岛屿架构示例">
<Header />
<!-- 静态内容,零JS -->
<main>
<section class="hero">
<h1>欢迎来到岛屿架构网站</h1>
<p>这是静态HTML,没有任何JavaScript负担</p>
</section>
<!-- 🏝️ ProductList 岛屿 - 使用 Astro 组件(无JS) -->
<!-- 纯 HTML/CSS 渲染的产品列表 -->
<ProductList products={products} />
<!-- 🏝️ Cart 岛屿 - React 组件 -->
<!-- 仅在可见时加载 React 运行时 -->
<Cart client:visible client:load="eager" />
<!-- 🏝️ Comments 岛屿 - Vue 组件 -->
<!-- 空闲时加载,不阻塞主线程 -->
<Comments comments={comments} client:idle />
<!-- 🏝️ Newsletter 岛屿 - Svelte 组件 -->
<!-- 仅在交互时加载 -->
<Newsletter client:only="svelte" />
</main>
</Layout>
<script>
// 只有这个script会打包到客户端
console.log('这是全局脚本,但非常小');
</script>
// ============ React 岛屿组件 ============
// src/components/Cart.tsx
import { useState, useEffect } from 'react';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export default function Cart() {
const [items, setItems] = useState<CartItem[]>([]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
// 仅在客户端执行
const savedCart = localStorage.getItem('cart');
if (savedCart) {
setItems(JSON.parse(savedCart));
}
}, []);
const addItem = (product: any) => {
const newItems = [...items, { ...product, quantity: 1 }];
setItems(newItems);
localStorage.setItem('cart', JSON.stringify(newItems));
};
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
<div className="cart">
<button
onClick={() => setIsOpen(!isOpen)}
className="cart-button"
>
🛒 购物车 ({items.length})
</button>
{isOpen && (
<div className="cart-dropdown">
{items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
{items.map(item => (
<div key={item.id} className="cart-item">
<span>{item.name}</span>
<span>¥{item.price} x {item.quantity}</span>
</div>
))}
<div className="cart-total">
总计: ¥{total.toFixed(2)}
</div>
<button className="checkout-btn">
去结算
</button>
</>
)}
</div>
)}
</div>
);
}
🔧 岛屿架构配置与优化
// ============ astro.config.mjs ============
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';
import tailwind from '@astrojs/tailwind';
import compress from 'astro-compress';
export default defineConfig({
// 集成多个框架
integrations: [
react({
// React 18 并发特性
experimentalReactChildren: true,
}),
vue(),
svelte(),
tailwind(),
compress()
],
// 输出模式:静态或服务端渲染
output: 'hybrid', // 混合模式,部分静态部分SSR
// 岛屿配置
experimental: {
// 优化岛屿预加载
optimizeHoistedScript: true,
// 部分水合
clientPrerender: true
},
// 构建优化
build: {
// 内联较小的样式
inlineStylesheets: 'auto',
// 分块策略
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'vue-vendor': ['vue'],
'svelte-vendor': ['svelte']
}
}
}
},
// 性能预算
vite: {
build: {
chunkSizeWarningLimit: 100, // 100KB
assetsInlineLimit: 4096 // 4KB
}
}
});
🎯 岛屿架构最佳实践
---
// ============ 性能优化模式 ============
// src/components/OptimizedIsland.astro
// 1. 懒加载策略
// client:load - 立即加载
// client:idle - 空闲时加载
// client:visible - 可见时加载
// client:media - 媒体查询匹配时加载
// client:only - 仅客户端渲染(跳过SSR)
// 2. 条件加载
const isMobile = Astro.request.headers.get('user-agent')?.includes('Mobile');
---
<!-- 移动端加载轻量组件 -->
{isMobile ? (
<MobileCarousel client:visible />
) : (
<DesktopCarousel client:visible />
)}
<!-- 仅在需要时加载重量级组件 -->
<HeavyChart client:visible />
<!-- 优先加载关键岛屿 -->
<CriticalIsland client:load />
<!-- 延迟加载非关键岛屿 -->
<NonCriticalIsland client:idle />
// ============ 岛屿通信 ============
// 岛屿之间可以通过事件总线通信
// src/lib/eventBus.ts
type EventCallback = (data: any) => void;
class EventBus {
private listeners = new Map<string, EventCallback[]>();
on(event: string, callback: EventCallback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
off(event: string, callback: EventCallback) {
const callbacks = this.listeners.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
}
}
emit(event: string, data: any) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(cb => cb(data));
}
}
}
export const eventBus = new EventBus();
// React 岛屿中
// src/components/ProductCard.tsx
import { eventBus } from '../lib/eventBus';
export default function ProductCard({ product }) {
const addToCart = () => {
eventBus.emit('cart:add', product);
};
return <button onClick={addToCart}>加入购物车</button>;
}
// Svelte 岛屿中
// src/components/Cart.svelte
import { eventBus } from '../lib/eventBus';
import { onMount } from 'svelte';
let items = [];
onMount(() => {
eventBus.on('cart:add', (product) => {
items = [...items, product];
});
});
</script>
<div class="cart">
购物车 ({items.length})
</div>
📊 架构对比与选型建议
| 架构 | 首屏JS | 交互响应 | 开发体验 | SEO | 适用场景 |
|---|---|---|---|---|---|
| 单体SPA | 大(~200KB) | 快 | 好 | 差 | 后台管理系统 |
| 微前端 | 中(~100KB) | 中 | 中 | 差 | 大型企业应用 |
| 岛屿架构 | 极小(~5KB) | 快 | 好 | 极佳 | 内容型网站、电商 |
| SSR/SSG | 中 | 中 | 好 | 极佳 | 博客、文档、营销页 |
| RSC(React) | 中 | 快 | 好 | 极佳 | 全栈应用 |
🎯 今日挑战
将一个传统 React SPA 重构为岛屿架构:
- 识别页面中的静态部分和交互部分
- 将交互部分拆分为独立的岛屿组件
- 为每个岛屿设置合适的加载策略(client:load/visible/idle)
- 实现岛屿间的通信机制
- 对比重构前后的性能指标(LCP、FID、CLS)
---
// 重构示例: 电商商品详情页
// 重构前: 整个页面都是 React 组件(~200KB JS)
// 重构后: 只有购物车、评论、推荐是岛屿(~15KB JS)
---
<Layout>
<!-- 静态内容:0KB JS -->
<section class="product-gallery">
<img src={product.image} alt={product.name} />
</section>
<section class="product-info">
<h1>{product.name}</h1>
<p>{product.description}</p>
<div class="price">¥{product.price}</div>
</section>
<!-- 🏝️ 购物车岛屿:45KB JS(React) -->
<Cart product={product} client:visible />
<!-- 🏝️ 评论岛屿:30KB JS(Vue) -->
<Comments productId={product.id} client:idle />
<!-- 🏝️ 推荐岛屿:20KB JS(Svelte) -->
<Recommendations productId={product.id} client:visible />
</Layout>
明日预告:全栈前端 - 使用 Next.js/Remix 构建完整应用
💡 架构箴言:"没有最好的架构,只有最合适的架构。岛屿架构是内容型网站的答案,但后台系统可能依然需要 SPA。"