Astro 是面向内容型网站的 Web 框架。配合 EmDash 使用时,Astro 会取代你的 WordPress 主题——负责模板、路由与渲染。
本指南把你已理解的 WordPress 概念映射到 Astro 基础之上。
关键范式转变
默认在服务器端渲染
与 PHP 一样,Astro 代码在服务器上运行。与 PHP 不同,它默认输出零 JavaScript 的静态 HTML。
不加就没有 JS
WordPress 会自动加载 jQuery 和主题脚本。除非你显式添加,否则 Astro 不会向浏览器发送任何脚本。
基于组件的架构
不再散落模板标签与 include,而是用可组合、自包含的组件来搭建。
基于文件的路由
无需重写规则或 query_vars。src/pages/ 的文件结构直接决定 URL。
项目结构
WordPress 主题往往是带“魔法文件名”的扁平结构。Astro 使用明确的目录:
| WordPress | Astro | 用途 |
|---|---|---|
index.php, single.php | src/pages/ | 路由(URL) |
template-parts/ | src/components/ | 可复用 UI 片段 |
header.php + footer.php | src/layouts/ | 页面外壳 |
style.css | src/styles/ | 全局 CSS |
functions.php | astro.config.mjs | 站点配置 |
典型的 Astro 项目:
src/
├── components/ # Reusable UI (Header, PostCard, etc.)
├── layouts/ # Page shells (Base.astro)
├── pages/ # Routes - files become URLs
│ ├── index.astro # → /
│ ├── posts/
│ │ ├── index.astro # → /posts
│ │ └── [slug].astro # → /posts/hello-world
│ └── [slug].astro # → /about, /contact, etc.
└── styles/
└── global.css
Astro 组件
.astro 文件相当于 PHP 模板。每个文件分两部分:
- Frontmatter(
---之间)— 服务器端代码,类似模板顶部的 PHP - 模板— 带表达式的 HTML,类似 PHP 模板的其余部分
---
// Frontmatter: runs on server, never sent to browser
interface Props {
title: string;
excerpt: string;
url: string;
}
const { title, excerpt, url } = Astro.props;
---
<!-- Template: outputs HTML -->
<article class="post-card">
<h2><a href={url}>{title}</a></h2>
<p>{excerpt}</p>
</article>
与 PHP 的主要区别:
- Frontmatter 是隔离的。 在那里声明的变量可在模板中使用,但代码本身不会到达浏览器。
- import 写在 frontmatter。 组件、数据、工具—都在顶部导入。
- 可以使用 TypeScript。 用
interface Props定义 prop 类型,获得编辑器补全与校验。
模板表达式
Astro 模板用 {花括号} 代替 <?php ?>。语法类似 JSX,但输出纯 HTML。
Astro
---
import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");
const showTitle = true;
---
{showTitle && <h1>Latest Posts</h1>}
{posts.length > 0 ? (
<ul>
{posts.map(post => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
</li>
))}
</ul>
) : (
<p>No posts found.</p>
)} PHP
<?php
$posts = new WP_Query(['post_type' => 'post']);
$show_title = true;
?>
<?php if ($show_title): ?>
<h1>Latest Posts</h1>
<?php endif; ?>
<?php if ($posts->have_posts()): ?>
<ul>
<?php while ($posts->have_posts()): $posts->the_post(); ?>
<li>
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
<?php else: ?>
<p>No posts found.</p>
<?php endif; ?> 表达式模式
| Pattern | 作用 |
|---|---|
{variable} | 输出值 |
{condition && <Element />} | 条件渲染 |
{condition ? <A /> : <B />} | If/else |
{items.map(item => <Li>{item}</Li>)} | 循环 |
Props 与插槽
组件通过 props(类似函数参数)和 slots(类似 do_action 插入点)接收数据。
Astro
---
interface Props {
title: string;
featured?: boolean;
}
const { title, featured = false } = Astro.props;
---
<article class:list={["card", { featured }]}>
<h2>{title}</h2>
<slot />
<slot name="footer" />
</article>用法:
<Card title="Hello" featured>
<p>This goes in the default slot.</p>
<footer slot="footer">Footer content</footer>
</Card> PHP
<?php
// Usage: get_template_part('template-parts/card', null, [
// 'title' => 'Hello',
// 'featured' => true
// ]);
$title = $args['title'] ?? '';
$featured = $args['featured'] ?? false;
$class = $featured ? 'card featured' : 'card';
?>
<article class="<?php echo esc_attr($class); ?>">
<h2><?php echo esc_html($title); ?></h2>
<?php
// No direct equivalent to slots.
// WordPress uses do_action() for similar patterns:
do_action('card_content');
do_action('card_footer');
?>
</article> Props 与 $args
在 WordPress 中,get_template_part() 通过 $args 数组传数据。Astro 的 props 有类型且可解构:
---
// Type-safe with defaults
interface Props {
title: string;
count?: number;
}
const { title, count = 10 } = Astro.props;
---
插槽与钩子
WordPress 用 do_action() 创建插入点。Astro 用插槽:
| WordPress | Astro |
|---|---|
do_action('before_content') | <slot name="before" /> |
| Default content area | <slot /> |
do_action('after_content') | <slot name="after" /> |
区别在于:插槽在调用处接收子元素,而 WordPress 钩子需要在别处单独调用 add_action()。
布局
布局用共同的 HTML 结构—<head>、页眉、页脚以及页面间共享部分—包裹页面。它取代 header.php + footer.php。
---
import "../styles/global.css";
interface Props {
title: string;
description?: string;
}
const { title, description = "My EmDash Site" } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<header>
<nav><!-- Navigation --></nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© {new Date().getFullYear()}</p>
</footer>
</body>
</html>
在页面中使用布局:
---
import Base from "../layouts/Base.astro";
---
<Base title="Home">
<h1>Welcome</h1>
<p>Page content goes in the slot.</p>
</Base>
样式
Astro 提供多种样式方案。最特别的是 作用域样式(scoped styles)。
作用域样式
<style> 标签里的样式会自动限定到该组件:
<article class="card">
<h2>Title</h2>
</article>
<style>
/* Only affects .card in THIS component */
.card {
padding: 1rem;
border: 1px solid #ddd;
}
h2 {
color: navy;
}
</style>
生成的 HTML 会带唯一类名,避免样式泄漏。减少特异性争夺战。
全局样式
站点级样式可创建 CSS 文件并在布局中导入:
---
import "../styles/global.css";
---
条件类
class:list 指令替代手动拼接类名字符串:
Astro
---
const { featured, size = "medium" } = Astro.props;
---
<article class:list={[
"card",
size,
{ featured, "has-border": true }
]}>输出:<article class="card medium featured has-border">
PHP
<?php
$classes = ['card', $size];
if ($featured) $classes[] = 'featured';
if (true) $classes[] = 'has-border';
?>
<article class="<?php echo esc_attr(implode(' ', $classes)); ?>"> 客户端 JavaScript
Astro 默认不发送 JavaScript。这是相对 WordPress 最大的思维转变。
添加交互
简单交互可添加 <script> 标签:
<button id="menu-toggle">Menu</button>
<nav id="mobile-menu" hidden>
<slot />
</nav>
<script>
const toggle = document.getElementById("menu-toggle");
const menu = document.getElementById("mobile-menu");
toggle?.addEventListener("click", () => {
menu?.toggleAttribute("hidden");
});
</script>
脚本会自动打包并去重。组件在页面上出现两次时,脚本只执行一次。
进阶:交互组件
更复杂的交互可按需加载 JavaScript 组件(React、Vue、Svelte)。这是可选项—多数站点只用 <script> 就够。
---
import SearchWidget from "../components/SearchWidget.jsx";
---
<!-- Only load JavaScript when the search box scrolls into view -->
<SearchWidget client:visible />
| Directive | JavaScript 何时加载 |
|---|---|
client:load | 页面加载后立即 |
client:visible | 组件进入视口时 |
client:idle | 浏览器空闲时 |
路由
Astro 使用 基于文件的路由。src/pages/ 下的文件即 URL:
| File | URL |
|---|---|
src/pages/index.astro | / |
src/pages/about.astro | /about |
src/pages/posts/index.astro | /posts |
src/pages/posts/[slug].astro | /posts/hello-world |
src/pages/[...slug].astro | Any path (catch-all) |
动态路由
对 CMS 内容,用方括号语法表示动态段:
---
import { getEmDashCollection, getEmDashEntry } from "emdash";
import Base from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
// For static builds, define which pages to generate
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts");
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
---
<Base title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
</Base>
与 WordPress 对比
| WordPress | Astro |
|---|---|
Template hierarchy (single-post.php) | Explicit file: posts/[slug].astro |
Rewrite rules + query_vars | File structure |
$wp_query determines template | URL maps directly to file |
add_rewrite_rule() | Create files or folders |
WordPress 概念对应何处
查找 WordPress 功能在 Astro/EmDash 中对应方式的参考表:
模板
| WordPress | Astro/EmDash |
|---|---|
| Template hierarchy | File-based routing in src/pages/ |
get_template_part() | Import and use components |
the_content() | <PortableText value={content} /> |
the_title(), the_*() | Access via post.data.title |
| Template tags | Template expressions {value} |
body_class() | class:list directive |
数据与查询
| WordPress | Astro/EmDash |
|---|---|
WP_Query | getEmDashCollection(type, filters) |
get_post() | getEmDashEntry(type, id) |
get_posts() | getEmDashCollection(type) |
get_the_terms() | Access via entry.data.categories |
get_post_meta() | Access via entry.data.fieldName |
get_option() | getSiteSettings() |
wp_nav_menu() | getMenu(location) |
可扩展性
| WordPress | Astro/EmDash |
|---|---|
add_action() | EmDash hooks, Astro middleware |
add_filter() | EmDash hooks |
add_shortcode() | Portable Text custom blocks |
register_block_type() | Portable Text custom blocks |
register_sidebar() | EmDash widget areas |
| Plugins | Astro integrations + EmDash plugins |
内容类型
| WordPress | Astro/EmDash |
|---|---|
register_post_type() | Create collection in admin UI |
register_taxonomy() | Create taxonomy in admin UI |
register_meta() | Add field to collection schema |
| Post status | Entry status (draft, published, etc.) |
| Featured image | Media reference field |
| Gutenberg blocks | Portable Text blocks |
小结
从 WordPress 跳到 Astro 变化很大,但脉络清楚:
- PHP 模板 → Astro 组件 — 同一思路(服务器代码 + HTML),结构更清晰
- 模板标签 → props 与 import — 显式数据流,少依赖全局
- 主题文件 → pages 目录 — URL 与文件结构一致
- 钩子 → 插槽与中间件 — 插入点更可预测
- 默认 jQuery → 默认零 JS — 有意添加交互
从 Getting Started 搭建第一个 EmDash 站点,或阅读 Working with Content 学习如何查询与渲染 CMS 数据。