源码深度解读 · withastro/astro
2026-04-01 | Astro Islands Architecture Deep Dive
第一部分:基础概念
第二部分:核心实现
第三部分:Islands 深入
第四部分:进阶与生态
Astro 是一个专注于内容驱动网站的 Web 框架,核心理念是"默认零 JS",通过 Islands 架构实现局部交互性。
核心特性
渲染模式
"Bring Your Own Framework" — Astro 不绑定任何 UI 框架,你可以混用 React、Vue、Svelte、Solid 等组件。
"Ship Less JavaScript" — 只发送必要的 JS。静态内容是纯 HTML,只有交互组件才会水合。
"Content First" — Astro 为博客、文档、营销站点等内容密集型场景优化,而非 SPA。
// .astro 文件示例 — HTML 为主,JS 组件为辅
---
import ReactCounter from '../components/ReactCounter.jsx';
import VueGallery from '../components/VueGallery.vue';
---
<h1>我的博客</h1>
<p>这段文字是零 JS 的纯 HTML</p>
<ReactCounter client:load /> {/* 只有这个会水合 */}
<VueGallery client:visible /> {/* 滚动到可见时才水合 */}
Islands 架构(2019年由 Preact 作者 Jason Miller 提出):页面大部分是静态 HTML,交互组件像"孤岛"一样散布其中。
┌─────────────────────────────────────────────────┐
│ 纯静态 HTML (零 JS) │
│ ┌─────────┐ ┌──────────────────────┐ │
│ │ 🏝️ 岛1 │ │ 静态内容... │ │
│ │ Counter │ │ 文字、图片、样式 │ │
│ │ (React) │ │ │ │
│ └─────────┘ │ ┌─────────┐ │ │
│ │ │ 🏝️ 岛2 │ │ │
│ 静态内容... │ │ Search │ │ │
│ 更多文字... │ │ (Vue) │ │ │
│ │ └─────────┘ │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────┘
↑ 只有 Island 会水合,其余是纯 HTML
传统 SPA 问题
传统 SSR 问题
Islands 的解决方案:只水合真正需要交互的组件,其余保持静态 HTML。JS 体积减少 50-90%
┌─────────────────────────────────────────────────────────┐
│ Astro Framework │
├─────────────────────────────────────────────────────────┤
│ 用户代码: .astro / .mdx / .ts / React / Vue / Svelte │
├─────────────────────────────────────────────────────────┤
│ 编译层: @astrojs/compiler → Vite 插件链 │
│ ├─ astro:build (核心编译) │
│ ├─ astro:build:css-hmr (CSS 热更新) │
│ ├─ astro:build:normal (虚拟模块解析) │
│ └─ 其他框架插件 (React/Vue/Svelte…) │
├─────────────────────────────────────────────────────────┤
│ 构建层: core/build → 静态页面 / SSR bundle │
├─────────────────────────────────────────────────────────┤
│ 运行时: astro-island (客户端水合) / View Transitions │
└─────────────────────────────────────────────────────────┘
| 包名 | 职责 |
|---|---|
| packages/astro | 核心框架:编译、构建、SSR 运行时 |
| packages/compiler | Astro 文件编译器 (TS → 严格模式) |
| packages/cli | 命令行工具 (dev/build/preview) |
| packages/integrations/* | 框架集成 (React/Vue/Svelte/Solid…) |
| packages/transformers | 内容转换器 (MDX/Markdown/remark…) |
| packages/prism | 代码高亮引擎 |
| packages/markdown | Markdown 解析器 |
| packages/create-astro | 项目脚手架 |
withastro/astro/
├── packages/
│ ├── astro/ # 核心框架
│ │ ├── src/
│ │ │ ├── index.ts # 公共 API 入口
│ │ │ ├── core/
│ │ │ │ ├── index.ts # 核心模块 re-export
│ │ │ │ ├── build/ # 构建流程
│ │ │ │ ├── dev/ # 开发服务器
│ │ │ │ ├── config/ # 配置系统
│ │ │ │ └── routing/ # 路由系统
│ │ │ ├── types/ # TypeScript 类型
│ │ │ ├── runtime/ # 客户端/服务端运行时
│ │ │ │ ├── client/ # 客户端 (astro-island)
│ │ │ │ └── server/ # 服务端 (SSR render)
│ │ │ └── vite-plugin-astro/ # Vite 插件
│ │ └── package.json
│ ├── compiler/ # Astro 编译器
│ ├── integrations/ # 框架集成
│ └── create-astro/ # 脚手架
└── package.json
// packages/astro/src/index.ts — 公共 API 入口
export { Astro } from './core/astro-jsx-runtime.js';
export { markHTMLString } from './runtime/server/html/index.js';
export { render } from './runtime/server/render/index.js';
// re-export 核心 API
export { defineConfig } from './core/config/config.js';
export { addVitePlugin } from './core/config/vite-plugin.js';
// 运行时类型
export type {
AstroClientBlueprint,
AstroGlobal,
AstroGlobalPartial,
} from './types/astro.js';
// 元数据
export type { ImageMetadata } from './types/public/images.js';
export type { CollectionEntry, ContentCollectionKey } from './content/index.js';
// 配置类型
export type { AstroConfig, AstroUserConfig } from './types/public/config.js';
// packages/astro/src/types/public/config.ts (简化)
export interface AstroUserConfig {
// 基础配置
site?: string;
base?: string;
trailingSlash?: 'never' | 'always' | 'ignore';
// 渲染配置
output?: 'static' | 'server' | 'hybrid';
adapter?: AstroAdapter;
// 构建配置
build?: {
inlineStylesheets?: 'always' | 'auto' | 'never';
assets?: string;
assetsPrefix?: string;
serverEntry?: string;
};
// Vite 集成
vite?: ViteConfigWithUserOptions;
// 模板引擎
markdown?: MarkdownConfig;
// 集成
integrations?: AstroIntegration[];
// 实验性功能
experimental?: ExperimentalConfig;
}
// packages/astro/src/core/config/config.ts
export function mergeConfig(
userConfig: AstroUserConfig,
root: string,
cmd: 'dev' | 'build' | 'preview',
): AstroSettings {
// 1. 设置默认值
const config: AstroConfig = {
root,
srcDir: new URL('./src/', root),
publicDir: new URL('./public/', root),
outDir: cmd === 'dev'
? new URL('./.astro/', root)
: new URL('./dist/', root),
site: userConfig.site ?? '',
base: userConfig.base ?? '/',
trailingSlash: userConfig.trailingSlash ?? 'ignore',
output: userConfig.output ?? 'static',
// ... 更多默认值
};
// 2. 验证配置
validateConfig(config);
// 3. 返回 settings
return { config, preferences: /* ... */ };
}
┌──────────────────────────────────────────────────────┐
│ 配置加载流程 │
├──────────────────────────────────────────────────────┤
│ 1. 读取 astro.config.mjs / astro.config.ts │
│ └→ 使用 Vite 的 loadConfigFromFile │
│ │
│ 2. 加载集成 (integrations) │
│ └→ 依次调用 integration.setup() │
│ └→ 收集 vite 插件 / hooks │
│ │
│ 3. 合并配置 │
│ └→ mergeConfig(userConfig, root, cmd) │
│ └→ 填充默认值 + 验证 │
│ │
│ 4. 创建 Vite 配置 │
│ └→ 统合 Astro 内置插件 + 集成插件 │
│ │
│ 5. 初始化 Astro 实例 │
│ └→ 创建 Settings 对象,传递给构建/开发流程 │
└──────────────────────────────────────────────────────┘
Astro 基于 Vite 构建,通过插件链处理 .astro 文件的编译、CSS 提取、脚本处理、HMR 等。
| 插件名 | 职责 | 阶段 |
|---|---|---|
| astro:build:css-hmr | CSS 依赖注册 & HMR | pre |
| astro:build | .astro 编译、虚拟模块加载 | pre |
| astro:build:normal | 虚拟模块 fallback 解析 | normal |
| @astrojs/react | React 组件编译 + JSX | normal |
| @astrojs/vue | Vue SFC 编译 | normal |
| @astrojs/svelte | Svelte 组件编译 | normal |
// packages/astro/src/vite-plugin-astro/index.ts
export default function astro({ settings, logger }): vite.Plugin[] {
const { config } = settings;
let server: vite.ViteDevServer | undefined;
let compile: (code: string, filename: string) => Promise<CompileAstroResult>;
// 每个文件独立的编译元数据
let astroFileToCompileMetadata = new Map<string, CompileMetadata>();
return [
{
name: 'astro:build',
enforce: 'pre', // 在其他插件之前执行
// 配置 Vite 环境
async configEnvironment(name, viteConfig) {
viteConfig.resolve ??= {};
viteConfig.resolve.conditions.push('astro'); // 添加 astro 条件
},
// Vite 配置就绪后初始化编译函数
async configResolved(viteConfig) {
compile = (code, filename) => {
return compileAstro({
astroConfig: config,
viteConfig,
filename,
source: code,
});
};
},
// ... transform / load / handleHotUpdate
}
];
}
// packages/astro/src/vite-plugin-astro/index.ts — transform
async handler(source, id) {
const parsedId = parseAstroRequest(id);
if (!parsedId.filename.endsWith('.astro')) return;
// ⚠️ 客户端环境:Astro 组件不可在浏览器中使用
if (this.environment.name === 'client') {
return {
code: `export default import.meta.env.DEV
? () => { throw new Error(
'Astro components cannot be used in the browser.') }
: {};`,
};
}
// 服务端:编译 .astro 文件
const transformResult = await compile(source, filename);
// 生成元数据
const astroMetadata = {
hydratedComponents: transformResult.hydratedComponents,
serverComponents: transformResult.serverComponents,
scripts: transformResult.scripts,
containsHead: transformResult.containsHead,
propagation: transformResult.propagation ? 'self' : 'none',
};
return { code: transformResult.code, map: transformResult.map,
meta: { astro: astroMetadata, vite: { lang: 'ts' } } };
}
Astro 编译 .astro 文件后,CSS 和脚本被拆分为虚拟模块,通过 ?astro&type=style&index=0 等 query string 访问。
// 虚拟模块 load 处理
async handler(id) {
const parsedId = parseAstroRequest(id);
const { query, filename } = parsedId;
const compileMetadata = astroFileToCompileMetadata.get(filename);
switch (query.type) {
case 'style': {
// CSS 虚拟模块
const result = compileMetadata.css[query.index];
result.dependencies?.forEach(dep => this.addWatchFile(dep));
return { code: result.code };
}
case 'script': {
// 脚本虚拟模块 (SSR 环境返回空)
if (isAstroServerEnvironment(this.environment)) {
return { code: '/* client script, empty in SSR */' };
}
const script = compileMetadata.scripts[query.index];
return { code: script.type === 'inline' ? script.code
: `import "${script.src}"` };
}
}
}
┌──────────────────────────────────────────────────────────┐
│ Astro Build Pipeline │
├──────────────────────────────────────────────────────────┤
│ 1. 加载配置 → mergeConfig() │
│ └→ 创建 AstroSettings + Vite 配置 │
│ │
│ 2. 初始化 Vite │
│ └→ 注册所有插件 (astro + 集成) │
│ │
│ 3. 扫描页面路由 │
│ └→ 发现所有 .astro/.mdx/.md 页面文件 │
│ └→ 构建路由清单 (RouteManifest) │
│ │
│ 4. 构建阶段 │
│ ├─ 步骤1: 创建入口点 (生成客户端/服务端入口) │
│ ├─ 步骤2: Vite 构建 (编译所有模块) │
│ ├─ 步骤3: 生成页面 (SSR 渲染每个页面) │
│ └─ 步骤4: 生成客户端 bundle │
│ │
│ 5. 输出 │
│ └→ 静态 HTML / SSR bundle + 客户端 JS │
└──────────────────────────────────────────────────────────┘
// packages/astro/src/core/build/index.ts (简化)
export async function build(config: AstroConfig): Promise<BuildResult> {
const settings = await createSettings(config, 'build');
const { builder } = settings;
// 1. 创建 Vite 构建器
const viteBuild = await createViteBuild(
settings, { mode: 'build' }
);
// 2. 构建步骤
const buildResult = await viteBuild.build({
buildSteps: async (vite, builder) => {
// 步骤1: 生成入口文件
await generateEntryModules(vite, builder);
// 步骤2: Vite 打包
await vite.build();
// 步骤3: SSR 渲染页面
const pages = await renderPages(vite, builder);
// 步骤4: 生成客户端 bundle
await generateClientBundle(vite, builder);
}
});
return buildResult;
}
步骤 1: generateEntryModules — 为每个页面生成 SSR 入口和客户端入口文件
步骤 2: Vite Build — 使用 Rollup 打包所有模块,Astro 插件在 pre 阶段编译 .astro
步骤 3: renderPages — 遍历所有路由,使用 SSR 渲染器生成 HTML
步骤 4: generateClientBundle — 为 Islands 组件生成独立的水合 JS bundle
关键点:每个 Island 组件被提取为独立的 chunk,只加载必要的框架运行时
┌──────────────────────────────────────────────────────┐
│ 单页面构建流程 │
├──────────────────────────────────────────────────────┤
│ │
│ index.astro │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Astro 编译器 │ → 模板 + 脚本 + 样式分离 │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 识别 Islands │ │
│ │ client:* 指令 │ │
│ └──────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ SSR Render │ │
│ │ → 纯 HTML (静态部分) │ │
│ │ → <astro-island> (Island) │ │
│ │ → <script> (水合代码) │ │
│ └─────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
output: 'static' (默认)
output: 'server'
output: 'hybrid' — 默认预渲染,但带 export const prerender = false 的页面走 SSR
// astro.config.mjs
export default defineConfig({
output: 'hybrid', // 或 'static' / 'server'
adapter: vercel(), // SSR 时需要适配器
});
客户端指令是 Astro Islands 的核心语法,通过 client:* 前缀声明组件的水合策略。
<!-- 页面加载时立即水合 -->
<SearchForm client:load />
<!-- 浏览器空闲时水合 -->
<Sidebar client:idle />
<!-- 滚动到可视区域时水合 -->
<HeavyChart client:visible />
<!-- 仅客户端渲染 (无 SSR) -->
<CookieBanner client:only="react" />
<!-- 默认:不水合,仅服务端渲染 -->
<StaticText />
| 指令 | 时机 | 适用场景 | SSR |
|---|---|---|---|
| client:load | 页面加载完成 | 关键交互 (搜索/导航) | ✅ |
| client:idle | requestIdleCallback | 低优先级交互 | ✅ |
| client:visible | IntersectionObserver | 页面底部/折叠区 | ✅ |
| client:only | 同 client:load | 依赖浏览器 API | ❌ |
| (无指令) | 不水合 | 纯静态内容 | ✅ |
最激进的水合策略 — 页面加载完成后立即加载组件 JS 并水合。适合首屏可见且需要立即交互的组件。
// SSR 输出示例 (简化)
<astro-island
uid="abc123"
component-url="/_astro/SearchForm.js"
component-export="default"
renderer-url="/_astro/client.js"
props="{"query":""}"
ssr
client="load"
>
<!-- 服务端预渲染的 HTML -->
<form><input placeholder="搜索..." /></form>
<!-- 客户端水合后替换此占位 -->
<script>initIsland(document.currentScript.parentElement)</script>
</astro-island>
注意:client:load 会在页面加载时阻塞,慎用于大型组件
使用 requestIdleCallback 在浏览器空闲时加载组件。平衡了交互性和性能。
// astro-island 内部实现 (简化)
function hydrateIdle(el) {
const observer = new IntersectionObserver((entries) => {
// 一旦可见,立即加载(不等待空闲)
if (entries[0].isIntersecting) {
observer.disconnect();
loadAndHydrate(el);
}
});
observer.observe(el);
// 同时注册 idle 回调
if ('requestIdleCallback' in window) {
requestIdleCallback(() => loadAndHydrate(el));
} else {
setTimeout(() => loadAndHydrate(el), 200);
}
}
适合:侧边栏、非关键的小型交互组件
使用 IntersectionObserver 在组件进入视口时才加载。最适合首屏以下的组件。
// IntersectionObserver 实现 (简化)
function hydrateVisible(el) {
const observer = new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
loadAndHydrate(entry.target);
}
}
},
{ rootMargin: '200px 0px' } // 提前 200px 触发
);
observer.observe(el);
}
优点
适合
client:only 跳过 SSR,组件只在客户端渲染。适合依赖浏览器 API(window/document)的组件。
<!-- client:only 需要指定框架 -->
<InteractiveMap client:only="react" data-lat="39.9" data-lng="116.4" />
<!-- SSR 输出: 只有占位符,无预渲染 HTML -->
<astro-island
uid="xyz789"
component-url="/_astro/InteractiveMap.js"
component-export="default"
renderer-url="/_astro/react.js"
props="{"data-lat":39.9,"data-lng":116.4}"
client="only"
>
<!-- 空!没有 SSR HTML -->
</astro-island>
权衡:无 SSR 意味着更长的 FMP (First Meaningful Paint),但避免了 SSR 不兼容问题
| 策略 | JS 时机 | FMP | TTI | JS 大小 |
|---|---|---|---|---|
| client:load | 立即 | ⭐⭐⭐ | ⭐⭐ | 全量 |
| client:idle | 空闲时 | ⭐⭐⭐ | ⭐⭐⭐ | 全量 |
| client:visible | 可见时 | ⭐⭐⭐ | ⭐⭐⭐ | 延迟加载 |
| client:only | 立即 | ⭐⭐ | ⭐⭐ | 全量 |
| (无指令) | 无 | ⭐⭐⭐ | ⭐⭐⭐ | 零 |
推荐策略:默认不加水合指令,只在真正需要交互时使用 client:visible 或 client:idle
<astro-island> 是 Islands 架构的核心 HTML 元素,作为 Island 组件的容器,负责水合调度。
<astro-island
uid="unique-id" // 唯一标识符
component-url="/_astro/Counter.js" // 组件 JS chunk
component-export="default" // 导出名
renderer-url="/_astro/react.js" // 框架运行时
props="{...}" // 序列化的 props (JSON)
ssr // 是否有 SSR 内容
client="load" // 水合策略
>
<!-- SSR 预渲染的 HTML / 占位符 -->
<template data-astro-cid-xxx></template>
</astro-island>
// astro-island 内部 (简化版)
// 根据 client 指令决定加载时机
function initIsland(el) {
const strategy = el.getAttribute('client');
switch (strategy) {
case 'load':
// 立即加载
loadAndHydrate(el);
break;
case 'idle':
// 空闲时加载,但进入视口后立即加载
requestIdleCallback(() => loadAndHydrate(el));
observeVisibility(el, () => loadAndHydrate(el));
break;
case 'visible':
// 可见时加载
observeVisibility(el, () => loadAndHydrate(el));
break;
case 'only':
// 立即加载,但跳过 SSR HTML
loadAndHydrate(el, { replace: true });
break;
}
}
┌──────────────────────────────────────────────────────────┐
│ Island 水合流程 │
├──────────────────────────────────────────────────────────┤
│ 1. 浏览器解析 HTML │
│ └→ 看到静态 HTML + <astro-island> 容器 │
│ │
│ 2. <script> 执行 │
│ └→ initIsland(el) 调用 │
│ └→ 根据 client 策略决定时机 │
│ │
│ 3. 动态 import 组件 JS │
│ └→ import(componentUrl) │
│ └→ import(rendererUrl) // React/Vue 运行时 │
│ │
│ 4. 框架水合 │
│ └→ React: hydrateRoot(container, element) │
│ └→ Vue: createApp().mount() │
│ └→ Svelte: new Component({ target }) │
│ │
│ 5. 移除 <astro-island> 包装器 (可选) │
└──────────────────────────────────────────────────────────┘
// 服务端渲染 Island 组件 (简化)
async function renderIsland(component, props, clientDirective) {
// 1. 如果不是 client:only,先做 SSR
let html = '';
if (clientDirective !== 'only') {
html = await renderFrameworkComponent(component, props);
}
// 2. 序列化 props 为 JSON (放在 data 属性中)
const serializedProps = JSON.stringify(props);
// 3. 生成 <astro-island> HTML
return `<astro-island
uid="${generateUID()}"
component-url="${component.chunkUrl}"
component-export="${component.exportName}"
renderer-url="${component.rendererUrl}"
props="${escapeHtml(serializedProps)}"
ssr
client="${clientDirective}"
>${html}</astro-island>`;
}
Island 组件的 Props 通过 JSON.stringify 序列化后嵌入 HTML 属性。客户端水合时反序列化并传入组件。
// 服务端: 序列化 Props
const props = {
title: '我的计数器',
initialValue: 0,
items: [1, 2, 3],
// ⚠️ 函数和复杂对象不能序列化!
};
// 嵌入 HTML
<astro-island props="${JSON.stringify(props)}" ...>
// 客户端: 反序列化
const rawProps = JSON.parse(el.getAttribute('props'));
// 传递给框架
hydrate(ReactComponent, el, rawProps);
限制:Props 必须是 JSON 可序列化的(字符串、数字、布尔、数组、简单对象)
// 编译 .astro 文件后生成的元数据
interface CompileMetadata {
// 需要水合的组件列表
hydratedComponents: HydratedComponent[];
// 仅服务端组件
serverComponents: string[];
// 页面脚本
scripts: ScriptMetadata[];
// CSS 样式
css: CSSMetadata[];
// 是否包含 <head>
containsHead: boolean;
// 样式传播模式
propagation: 'self' | 'none';
// clientOnly 组件
clientOnlyComponents: HydratedComponent[];
}
interface HydratedComponent {
resolvedPath: string; // 组件文件路径
displayName: string; // 组件名
hydrate: string; // 水合函数名
componentUrl: string; // 构建后的 JS chunk URL
rendererUrl: string; // 框架运行时 URL
}
脚本处理
<script> 自动提取为虚拟模块样式处理
?astro&type=style&index=0// 样式虚拟模块 — 支持 Vite 的 cssScopeTo 做树摇
return {
code: result.code,
meta: result.isGlobal ? undefined : {
vite: {
cssScopeTo: [filename, 'default'],
},
},
};
Astro 组件中的 <style> 默认是 scoped 的。编译时通过添加 data-astro-cid-xxx 属性实现样式隔离。
<!-- 源码 -->
<style>
h1 { color: orange; }
</style>
<h1>Hello Astro</h1>
<!-- 编译后 -->
<style>
h1[data-astro-cid-a1b2c3] { color: orange; }
</style>
<h1 data-astro-cid-a1b2c3>Hello Astro</h1>
全局样式:使用 <style is:global> 声明不 scoped 的样式
// astro:build:css-hmr 插件
{
name: 'astro:build:css-hmr',
enforce: 'pre',
applyToEnvironment(env) {
return env.name === 'client';
},
transform: {
filter: {
id: { include: [/(?:\\?|&)astro(?:&|=|$)/] },
},
async handler(_source, id) {
const parsedId = parseAstroRequest(id);
const compileMetadata = astroFileToCompileMetadata
.get(parsedId.filename);
if (compileMetadata && parsedId.query.type === 'style') {
const result = compileMetadata.css[parsedId.query.index];
// 注册 CSS 依赖,文件变化时触发 HMR
result.dependencies?.forEach((dep) => {
this.addWatchFile(dep);
});
}
},
},
}
Astro 在构建时通过 applyToEnvironment 和 environment.name 区分客户端/服务端,确保组件不会在错误的环境中执行。
// 客户端环境名常量
const ASTRO_VITE_ENVIRONMENT_NAMES = {
client: 'client',
server: 'server',
};
// 在 astro:build 插件中检测环境
applyToEnvironment(environment) {
return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client;
}
// 客户端环境 → Astro 组件返回空模块
if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) {
return {
code: `export default import.meta.env.DEV
? () => { throw new Error(
'Astro components cannot be used in the browser.') }
: {};`,
};
}
Astro 开发服务器基于 Vite Dev Server,增加了 Astro 文件编译、HMR、和路由处理。
// 开发服务器初始化 (简化)
async function startDevServer(settings: AstroSettings) {
const viteConfig = createViteConfig(settings, 'dev');
const server = await vite.createServer({
...viteConfig,
server: {
host: settings.config.server?.host,
port: settings.config.server?.port ?? 4321,
},
});
await server.listen();
return server;
}
// 文件变更监听
server.watcher.on('unlink', (filename) => {
// 清理编译缓存
astroFileToCompileMetadata.delete(filename);
});
// packages/astro/src/vite-plugin-astro/hmr.ts (简化)
async function handleHotUpdate(ctx, { logger, compile, astroFileToCompileMetadata }) {
const { file, read, server } = ctx;
// 如果变更的是 .astro 文件
if (file.endsWith('.astro')) {
// 重新编译
const source = await read();
await compile(source, file);
// 通知客户端更新
const timestamp = Date.now();
server.ws.send({
type: 'update',
updates: [{
type: 'js-update',
path: file,
acceptedPath: file,
timestamp,
}],
});
logger.info(`[HMR] ${file} updated`);
}
}
Content Collections 是 Astro 的内容管理方案,提供类型安全的 Markdown/MDX 内容查询。
// src/content/config.ts — 定义集合 schema
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
// 在 .astro 中查询
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);
Astro 内置了基于浏览器 View Transitions API 的页面过渡效果,无需额外 JS 框架。
// src/layouts/Base.astro
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<title>{title}</title>
<ViewTransitions /> {/* 一行启用! */}
</head>
<body>
<slot />
</body>
</html>
// 自定义过渡
<ViewTransitions
transition:animate="morph"
transition:fallback="fade"
/>
特性:自动处理前进/后退动画、保持滚动位置、支持生命周期钩子
构建时优化
运行时优化
Astro 最强大的特性:不添加任何 client 指令的组件完全零 JS。这是与传统框架的根本区别。
// 这个页面完全零 JS!
---
const posts = await getCollection('blog');
---
<html>
<body>
<h1>博客列表</h1>
{posts.map(post => (
<article>
<h2>{post.data.title}</h2>
<p>{post.data.date}</p>
</article>
))}
{/* 只有 Counter 需要 JS */}
<Counter client:visible />
</body>
</html>
// 输出: 纯 HTML + Counter.js (仅一个 chunk)
┌─────────────────────────────────────────────────────┐
│ 完整水合 (传统 SSR 框架) │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ H1 │ Nav │Post │Form │Side │Foot │ ← 全部水合 │
│ │ JS │ JS │ JS │ JS │ JS │ JS │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┘ │
│ 总 JS: ~150KB │
├─────────────────────────────────────────────────────┤
│ 部分水合 (Astro Islands) │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ H1 │ Nav │Post │Form │Side │Foot │ │
│ │ 0KB │ 0KB │ 0KB │ 12KB│ 0KB │ 0KB │ ← 只有 Form │
│ └─────┴─────┴─────┴─────┴─────┴─────┘ │
│ 总 JS: ~12KB (减少 92%) │
└─────────────────────────────────────────────────────┘
| 特性 | Astro | Next.js | Nuxt | Remix |
|---|---|---|---|---|
| 默认 JS | 零 | 有 | 有 | 有 |
| 水合方式 | Islands | 整页/部分 | 整页 | 整页 |
| 多框架 | ✅ | ❌ (React) | ❌ (Vue) | ❌ (React) |
| SSG | ✅ | ✅ | ✅ | ✅ |
| SSR | ✅ | ✅ | ✅ | ✅ |
| 适用场景 | 内容站 | 通用 | 通用 | Web 应用 |
UI 框架
部署适配器
其他
推荐做法
性能建议
1. 在 Astro 组件中添加事件监听器
// ❌ Astro 组件不支持客户端事件
<button onClick={() => count++}>Click</button>
// ✅ 使用 client 指令
<Button client:idle /> // Button 是 React/Vue 组件
2. 传递不可序列化的 Props
// ❌ 函数不能通过 props 传递给 Island
<Chart client:load onData={handleData} />
// ✅ 使用事件或自定义属性
<Chart client:load data={jsonArray} />
架构核心
设计亮点
Astro Islands 架构重新定义了内容驱动网站的开发方式:用最少的 JS 实现最大的交互性。源码体现了"编译时做尽可能多的工作,运行时只做必要的"这一哲学。
源码地址
https://github.com/withastro/astro