astro 语法#

.astro 是增强版 HTML - 可以写逻辑、导入组件、自动优化,但最终生成纯 HTML

---
// .astro 文件 - 多了这些
// 1. 代码块(运行在服务端)
const name = 'World'
const users = ['Alice', 'Bob', 'Charlie']
---
<!-- 2. 模板语法 - 可以嵌入 JavaScript 表达式 -->
<h1>Hello {name}</h1>
<!-- 3. 动态渲染列表 -->
<ul>
{users.map(user => <li>{user}</li>)}
</ul>
<!-- 4. 条件渲染 -->
{users.length > 0 ? (
<p>有 {users.length} 个用户</p>
) : (
<p>暂无用户</p>
)}
<!-- 5. 导入其他组件 -->
<NavBar />
<!-- 6. 作用域样式 -->
<style>
/* 这个样式只影响本组件 */
h1 { color: red; }
</style>
<!-- 7. 客户端脚本 -->
<script>
console.log('运行在浏览器')
</script>

PixPin_2026-05-05_04-13-48

┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 0:源代码输入 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Button.astro │
│ ┌─────────────────────────────────────────┐ │
│ │ --- │ │
│ │ import Base from './Base.astro' │ │
│ │ interface Props { variant?: string } │ │
│ │ const { variant = 'primary' } = Astro.props │
│ │ --- │ │
│ │ <button class:active={true}>Click</button> │
│ │ <style>.btn { color: red; }</style> │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 1:文件解析 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Astro 编译器读取 .astro 文件,识别四个部分: │
│ │
│ ┌──────────────────┐ │
│ │ 1. Frontmatter │ --- ... --- (组件脚本,服务端执行) │
│ ├──────────────────┤ │
│ │ 2. HTML 模板 │ <button>...</button> (UI 模板) │
│ ├──────────────────┤ │
│ │ 3. Style 块 │ <style>...</style> (组件样式) │
│ ├──────────────────┤ │
│ │ 4. Script 块 │ <script>...</script> (客户端脚本) │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 2:TypeScript 编译(Frontmatter 处理) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 2.1:识别代码类型 │ │
│ │ │ │
│ │ interface Props { variant?: string } → 类型定义 │ │
│ │ import Base from './Base.astro' → 实际代码 │ │
│ │ const { variant } = Astro.props → 实际代码 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 2.2:类型检查 │ │
│ │ │ │
│ │ TypeScript 检查 interface 定义是否正确 │ │
│ │ • variant 类型是否匹配? │ │
│ │ • 导入的模块是否存在? │ │
│ │ │ │
│ │ ✅ 通过 → 继续 │ │
│ │ ❌ 失败 → 报错,停止构建 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 2.3:编译转换 │ │
│ │ │ │
│ │ 移除所有类型定义(interface/type) │ │
│ │ 将 TypeScript 语法编译为 JavaScript │ │
│ │ 执行代码,收集: │ │
│ │ • Props 定义 │ │
│ │ • 组件需要接收哪些 Props? │ │
│ │ • 哪些 Props 有默认值? │ │
│ │ • 变量值(如 variant = 'primary') │ │
│ │ │ │
│ │ 输出:JavaScript 逻辑代码 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 3:模板编译(HTML 模板处理) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 遍历 HTML 模板,识别并处理以下语法: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 A:Astro 指令 │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ A1. class:list │ │
│ │ <div class:list={['base', isActive && 'active']}> │ │
│ │ → 计算数组:['base', true && 'active'] → ['base', 'active'] │ │
│ │ → 生成:<div class="base active"> │ │
│ │ │ │
│ │ A2. set:html / set:text │ │
│ │ <div set:html={htmlString} /> │ │
│ │ → 验证 HTML 安全性 │ │
│ │ → 直接插入 HTML 字符串 │ │
│ │ │ │
│ │ A3. client:*(客户端指令) │ │
│ │ <Interactive client:load /> │ │
│ │ → 标记为需要客户端激活 │ │
│ │ → 生成占位符 <astro-island> │ │
│ │ → 记录组件路径和 Props │ │
│ │ │ │
│ │ A4. transition:*(视图过渡) │ │
│ │ <div transition:name="hero"> │ │
│ │ → 添加视图过渡标记 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 B:JavaScript 表达式 │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ B1. 变量插值 {variable} │ │
│ │ <h1>{title}</h1> │ │
│ │ → 替换为变量的值 │ │
│ │ │ │
│ │ B2. 条件渲染 {condition && <div />} │ │
│ │ {isLoggedIn && <span>欢迎</span>} │ │
│ │ → isLoggedIn = true → 保留 <span> │ │
│ │ → isLoggedIn = false → 完全删除 │ │
│ │ │ │
│ │ B3. 三元表达式 {cond ? A : B} │ │
│ │ {user ? <div>A</div> : <div>B</div>} │ │
│ │ → 根据条件只保留一个分支 │ │
│ │ │ │
│ │ B4. 数组循环 {arr.map(item => <li>{item}</li>)} │ │
│ │ {['A','B'].map(i => <li>{i}</li>)} │ │
│ │ → 展开为 <li>A</li><li>B</li> │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 输出:处理后的 HTML 字符串 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 4:组件系统处理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 4.1:识别组件标签 │ │
│ │ │ │
│ │ <BaseButton variant="primary">Click</BaseButton> │ │
│ │ │ │
│ │ 判断类型: │ │
│ │ • HTML 内置标签 (<div>, <span>) → 直接输出 │ │
│ │ • Astro 组件 (<BaseButton>) → 递归编译 │ │
│ │ • 框架组件 (<ReactButton />) → 特殊处理 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 4.2:处理 Astro 组件 │ │
│ │ │ │
│ │ 子组件 = 递归执行阶段 1-3 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 读取 BaseButton.astro │ │ │
│ │ │ 2. 编译其 Frontmatter │ │ │
│ │ │ 3. 编译其模板 │ │ │
│ │ │ 4. 接收 Props { variant: 'primary' } │ │ │
│ │ │ 5. 处理 children 'Click' │ │ │
│ │ │ 6. 返回生成的 HTML │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 合并子组件 HTML 到父组件 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 4.3:处理框架组件(React/Vue/Svelte) │ │
│ │ │ │
│ │ <ReactButton onClick={handleClick}>Click</ReactButton> │ │
│ │ │ │
│ │ • 提取 Props 和 children │ │
│ │ • 生成包装器 HTML:<astro-island> │ │
│ │ • 序列化 Props 为 JSON │ │
│ │ • 记录需要客户端水合 │ │
│ │ • 等待后续生成 JS 入口文件 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 5:样式处理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 收集所有 <style> 标签,分类处理: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 1:普通样式(默认作用域) │ │
│ │ │ │
│ │ <style> │ │
│ │ .btn { color: red; } │ │
│ │ </style> │ │
│ │ │ │
│ │ → 生成唯一 ID:data-astro-abc123 │ │
│ │ → 重写选择器:.btn[data-astro-abc123] { color: red; } │ │
│ │ → 给对应 HTML 添加属性 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 2:内联样式(<style is:inline>) │ │
│ │ │ │
│ │ → 保持原样,不添加作用域 │ │
│ │ → 原样输出到 HTML 中 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 3:全局样式(src/styles/) │ │
│ │ │ │
│ │ → 不添加作用域 │ │
│ │ → 提取到独立 CSS 文件 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 生产模式额外处理: │
│ • 合并相同组件的样式 │
│ • 压缩 CSS(移除空格、注释) │
│ • 添加浏览器前缀 │
│ • 生成独立 .css 文件,添加哈希(如 Button.a1b2c3.css) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 6:脚本处理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 收集所有 <script> 标签,分类处理: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 1:普通脚本 │ │
│ │ │ │
│ │ <script> │ │
│ │ console.log('hello') │ │
│ │ </script> │ │
│ │ │ │
│ │ → 提取内容到独立文件 │ │
│ │ → 添加 type="module" │ │
│ │ → 处理 import 语句 │ │
│ │ → 生产模式:压缩、Tree Shaking │ │
│ │ → 输出:/_astro/button.xyz789.js │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 2:内联脚本(<script is:inline>) │ │
│ │ │ │
│ │ → 保持原样,不提取 │ │
│ │ → 原样输出到 HTML 中 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 类型 3:框架组件脚本(React/Vue) │ │
│ │ │ │
│ │ → 生成组件入口文件 │ │
│ │ → 包含水合逻辑 │ │
│ │ → 输出:/_astro/Component.hash.js │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 7:最终输出 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 组装所有编译产物: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 最终 HTML 文件 │ │
│ │ │ │
│ │ <!DOCTYPE html> │ │
│ │ <html> │ │
│ │ <head> │ │
│ │ <!-- 样式链接 --> │ │
│ │ <link rel="stylesheet" href="/_astro/button.a1b2c3.css"> │ │
│ │ </head> │ │
│ │ <body> │ │
│ │ <!-- 组件 HTML --> │ │
│ │ <button class="btn btn-primary" data-astro-abc123> │ │
│ │ 点击 │ │
│ │ </button> │ │
│ │ │ │
│ │ <!-- 框架组件占位符 --> │ │
│ │ <astro-island data-props="..."></astro-island> │ │
│ │ │ │
│ │ <!-- 脚本链接 --> │ │
│ │ <script type="module" src="/_astro/button.xyz789.js"></script>│ │
│ │ </body> │ │
│ │ </html> │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ dist/ 目录结构: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ dist/ │ │
│ │ ├── index.html # 页面 HTML │ │
│ │ ├── about.html │ │
│ │ ├── _astro/ # 优化后的资源 │ │
│ │ │ ├── button.a1b2c3.css # 样式文件 │ │
│ │ │ ├── button.xyz789.js # 脚本文件 │ │
│ │ │ ├── logo.abc123.webp # 优化后的图片 │ │
│ │ │ └── chunk.xxx.js # 公共代码块 │ │
│ │ └── public/ # 静态资源 │ │
│ │ └── favicon.svg │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 阶段 8:浏览器运行时 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 8.1:加载页面 │ │
│ │ │ │
│ │ 浏览器请求 → 服务器返回 HTML → 解析 DOM │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 8.2:加载资源 │ │
│ │ │ │
│ │ • 并行加载 CSS 文件 │ │
│ │ • 并行加载 JS 文件 │ │
│ │ • 加载图片等静态资源 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 8.3:执行脚本 │ │
│ │ │ │
│ │ • 执行普通 JS 脚本 │ │
│ │ • 水合框架组件: │ │
│ │ - 扫描 <astro-island> 占位符 │ │
│ │ - 加载对应的组件 JS │ │
│ │ - 激活组件(绑定事件、状态等) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 最终结果 │ │
│ │ │ │
│ │ 用户看到完整页面,可交互 │ │
│ │ SEO 搜索引擎看到完整 HTML 内容 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
运行环境位置能做什么不能做什么
服务端(Astro 组件脚本)构建时的 Node.js读文件、访问数据库、调用 API、导入模块访问 windowdocumentlocalStorage
客户端(浏览器)用户的浏览器DOM 操作、事件监听、用户交互读服务器文件、访问环境变量

核心增强#

一、组件化#

组件化 = 可复用的 UI 片段 + 封装的状态和样式

1、Props 传递机制#

interface 是 TypeScript 的类型系统,不是 JavaScript 语法。只在编译时运行(类型检查),运行时完全消失

Props 让 Astro 组件像函数一样可组合,父组件通过属性传参,子组件通过 Astro.props 接收,构建时生成最终的 HTML

---
// 1. 定义组件 Props 类型
interface Props {
title: string
onClick?: () => void
}
// 2. 使用类型
const { title, onClick } = Astro.props
// 3. 类型守卫(编译时)
if (typeof onClick === 'function') {
// 运行时检查,不是类型检查
}
---
<!-- 4. 模板中使用 -->
<h1>{title}</h1>

案例1:博客文章卡片

PostCard.astro
---
interface Props {
title: string
excerpt: string
date: Date
tags: string[]
coverImage?: string
readingTime: number
}
const {
title,
excerpt,
date,
tags,
coverImage,
readingTime
} = Astro.props
---
<article class="post-card">
{coverImage && (
<img src={coverImage} alt={title} class="cover" />
)}
<div class="content">
<h2>{title}</h2>
<p class="excerpt">{excerpt}</p>
<div class="meta">
<time datetime={date.toISOString()}>
{date.toLocaleDateString('zh-CN')}
</time>
<span class="reading-time">{readingTime} 分钟阅读</span>
</div>
<div class="tags">
{tags.map(tag => (
<span class="tag">{tag}</span>
))}
</div>
</div>
</article>
---
// 在列表页使用
import PostCard from '../components/PostCard.astro'
const posts = await getCollection('blogs')
---
<div class="posts-grid">
{posts.map(post => (
<PostCard
title={post.data.title}
excerpt={post.data.description}
date={post.data.pubDate}
tags={post.data.tags}
coverImage={post.data.cover?.src}
readingTime={post.data.minutesRead}
/>
))}
</div>

案例2:多层传递

---
// Level1.astro - 顶层组件
import Level2 from './Level2.astro'
const userData = {
name: '李四',
level: 'gold',
points: 1500
}
---
<Level2 user={userData} />
---
// Level2.astro - 中间组件
import Level3 from './Level3.astro'
const { user } = Astro.props
---
<div>
<h2>用户{user.name}</h2>
<Level3 user={user} />
</div>
---
// Level3.astro - 底层组件
const { user } = Astro.props
---
<div class="user-detail">
<p>等级{user.level}</p>
<p>积分{user.points}</p>
</div>
2、样式封装#
<style>
/* 这个样式只会影响当前组件 */
.btn {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
.btn-primary {
background: blue;
color: white;
}
</style>

编译后:

<!-- Astro 自动添加唯一属性 -->
<style>
.btn[data-astro-abc123] { ... }
.btn-primary[data-astro-abc123] { ... }
</style>
<button class="btn btn-primary" data-astro-abc123>
点击我
</button>
3、组件嵌套#
---
// Card.astro - 使用 Button 组件
import Button from './Button.astro'
const { title } = Astro.props
---
<div class="card">
<h3>{title}</h3>
<p><slot /></p>
<Button variant="primary">确认</Button>
<Button variant="secondary">取消</Button>
</div>

二、插槽系统#

插槽 = 占位符,让父组件可以”注入”内容到子组件的指定位置

PixPin_2026-05-04_00-43-21

1、默认插槽(任意内容)#
Layout.astro
---
---
<div class="layout">
<header>
<slot name="header" /> <!-- 只接收 slot="header" 的内容 -->
</header>
<main>
<slot /> <!-- 接收没有 name 的内容 -->
</main>
<footer>
<slot name="footer" /> <!-- 只接收 slot="footer" 的内容 -->
</footer>
</div>
// 使用 Layout
<Layout>
<div slot="header">导航栏</div>
<article>主要内容</article> <!-- 自动进入默认 slot -->
<div slot="footer">版权信息</div>
</Layout>
2、具名插槽(命名区域)#
layouts/Documentation.astro
---
---
<div class="doc-layout">
<!-- 定义多个命名区域 -->
<aside>
<slot name="sidebar" /> <!-- 侧边栏区域 -->
</aside>
<main>
<slot name="before-content" /> <!-- 内容前区域 -->
<slot /> <!-- 主要内容区域 -->
<slot name="after-content" /> <!-- 内容后区域 -->
</main>
<div class="extra">
<slot name="footer-extra" /> <!-- 额外区域 -->
</div>
</div>
// 使用 - 可以填充各种区域
<Documentation>
<!-- ✅ 填充定义的侧边栏 -->
<div slot="sidebar">
<ul>目录</ul>
</div>
<!-- ✅ 填充内容前区域 -->
<div slot="before-content">
<div class="notice">提示信息</div>
</div>
<!-- ✅ 默认插槽 - 主要内容 -->
<h1>文档标题</h1>
<p>文档内容...</p>
<!-- ✅ 填充内容后区域 -->
<div slot="after-content">
<div class="share">分享组件</div>
</div>
<!-- ✅ 填充额外区域 -->
<div slot="footer-extra">
<div class="related">相关文章</div>
</div>
</Documentation>

三、模版指令#

模板指令 = 编译时的 DOM 操作标记,构建时转化为静态 HTML

PixPin_2026-05-04_00-52-57

Astro 指令最终都会被编译成纯 HTML(以及可选的客户端 JavaScript),这就是 Astro”零 JS 默认”的核心原理。这些指令是 Astro 团队预先实现的,就像 Vue 的 v-if、React 的 className 一样

唯一会产生 JS 的指令:client:load 等客户端指令

指令作用示例
class:list动态类名列表class:list={['base', active && 'active']}
class:value条件类名class:active={isActive}
set:html设置 innerHTMLset:html={htmlString}
set:text设置 textContentset:text={textString}
is:inline禁用组件包装<style is:inline>
client:load客户端加载组件<Component client:load />
client:visible可见时加载<Component client:visible />
client:idle空闲时加载<Component client:idle />
client:media媒体查询匹配时加载<Component client:media="(max-width: 768px)" />
transition:name视图过渡动画<div transition:name="hero">
transition:animate自定义过渡动画<div transition:animate="slide">

指令的编译过程:

// 1. 源代码
const condition = true
const result = <div class:active={condition}>内容</div>
// 2. Astro 编译时执行
// condition = true 被计算
// 3. 生成 HTML
`<div class="active">内容</div>`
// 4. 最终输出(无任何运行时痕迹)

四、布局支持#

布局 = 页面包装器,提供一致的页面结构

PixPin_2026-05-04_00-48-12.png

Astro 博客开发流程#

  1. 构思内容结构

    • 首页、文章列表、关于页、标签页
  2. 创建 Astro 项目(使用官方博客模板)

    Terminal window
    npm create astro@latest -- --template blog
  3. 编写布局(Layout)
    src/layouts/BlogLayout.astro – 包含 <header><main><footer>

  4. 添加 Markdown 文章
    src/content/posts/第一篇.md

    ---
    title: '我的第一篇博客'
    pubDate: 2025-01-01
    ---
    这里是内容...
  5. 编写动态路由(自动生成每篇文章的页面)
    src/pages/posts/[...slug].astro – 读取 Markdown 并渲染

  6. 添加交互(可选)

    • 比如:评论区(React 组件 + client:load
    • 比如:图片灯箱(Vue 组件 + client:visible
  7. 优化和构建

    Terminal window
    npm run build # 输出到 dist/
  8. 部署(可免费托管到 Netlify、Vercel、Cloudflare Pages)