面向 WordPress 开发者的 Astro

本页内容

Astro 是一个用于构建内容驱动网站的 Web 框架。使用 EmDash 时,Astro 替代了您的 WordPress 主题 — 它处理模板、路由和渲染。

本指南通过将 Astro 基础知识映射到您已经理解的 WordPress 概念来进行教学。

关键范式转变

默认服务器渲染

像 PHP 一样,Astro 代码在服务器上运行。与 PHP 不同的是,它默认输出零 JavaScript 的静态 HTML。

除非添加否则零 JS

WordPress 自动加载 jQuery 和主题脚本。Astro 不会向浏览器发送任何内容, 除非您明确添加它。

基于组件的架构

不再使用分散的模板标签和包含文件,而是使用可组合的、自包含的 组件进行构建。

基于文件的路由

没有重写规则或 query_varssrc/pages/ 中的文件结构直接定义您的 URL。

项目结构

WordPress 主题具有扁平结构和魔术文件名。Astro 使用明确的目录:

WordPressAstro用途
index.phpsingle.phpsrc/pages/路由(URL)
template-parts/src/components/可重用的 UI 组件
header.php + footer.phpsrc/layouts/页面包装器
style.csssrc/styles/全局 CSS
functions.phpastro.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 文件是 Astro 相当于 PHP 模板的东西。每个文件有两部分:

  1. Frontmatter(在 --- 围栏之间)— 服务器端代码,类似于模板顶部的 PHP
  2. 模板 — 带有表达式的 HTML,类似于 PHP 模板的其余部分

以下组件在 frontmatter 中声明类型化的 props 并在模板中渲染它们:

---
// 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 是隔离的。 在那里声明的变量在模板中可用,但代码本身永远不会到达浏览器。
  • 导入放在 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; ?>

表达式模式

模式用途
{variable}输出值
{condition && <Element />}条件渲染
{condition ? <A /> : <B />}If/else
{items.map(item => <Li>{item}</Li>)}循环

Props 和 Slots

组件通过 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>

以下标记使用该组件,传递默认插槽和命名的 footer 插槽:

<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 vs $args

在 WordPress 中,get_template_part() 通过 $args 数组传递数据。Astro props 是类型化的并解构的:

---
// Type-safe with defaults
interface Props {
  title: string;
  count?: number;
}
const { title, count = 10 } = Astro.props;
---

Slots vs Hooks

WordPress 使用 do_action() 创建插入点。Astro 使用插槽:

WordPressAstro
do_action('before_content')<slot name="before" />
默认内容区域<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>&copy; {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 提供多种样式方法。最独特的是 作用域样式

作用域样式

<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>

脚本会自动打包和去重。如果这个组件在页面上出现两次,脚本只运行一次。

高级交互组件

对于更复杂的交互性,Astro 可以按需加载 JavaScript 组件(React、Vue、Svelte)。这是可选的 — 大多数站点仅使用 <script> 标签就可以正常工作。以下页面仅在滚动到视图时才加载组件:

---
import SearchWidget from "../components/SearchWidget.jsx";
---
<!-- Only load JavaScript when the search box scrolls into view -->
<SearchWidget client:visible />
指令JavaScript 何时加载
client:load页面加载时立即
client:visible当组件进入视口时
client:idle当浏览器空闲时

路由

Astro 使用 基于文件的路由src/pages/ 中的文件变成 URL:

文件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任意路径(全捕获)

动态路由

对于 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 比较

WordPressAstro
模板层次结构(single-post.php明确的文件:posts/[slug].astro
重写规则 + query_vars文件结构
$wp_query 确定模板URL 直接映射到文件
add_rewrite_rule()创建文件或文件夹

WordPress 概念存在的位置

查找 WordPress 功能的 Astro/EmDash 等价物的参考:

模板

WordPressAstro/EmDash
模板层次结构src/pages/ 中基于文件的路由
get_template_part()导入并使用组件
the_content()<PortableText value={content} />
the_title()the_*()通过 post.data.title 访问
模板标签模板表达式 {value}
body_class()class:list 指令

数据和查询

WordPressAstro/EmDash
WP_QuerygetEmDashCollection(type, filters)
get_post()getEmDashEntry(type, id)
get_posts()getEmDashCollection(type)
get_the_terms()通过 entry.data.categories 访问
get_post_meta()通过 entry.data.fieldName 访问
get_option()getSiteSettings()
wp_nav_menu()getMenu(location)

可扩展性

WordPressAstro/EmDash
add_action()EmDash 钩子、Astro 中间件
add_filter()EmDash 钩子
add_shortcode()Portable Text 自定义块
register_block_type()Portable Text 自定义块
register_sidebar()EmDash 小部件区域
插件Astro 集成 + EmDash 插件

内容类型

WordPressAstro/EmDash
register_post_type()在管理 UI 中创建集合
register_taxonomy()在管理 UI 中创建分类法
register_meta()将字段添加到集合架构
文章状态条目状态(draft、published 等)
特色图片媒体引用字段
Gutenberg 块Portable Text 块

概念映射

本指南涵盖的主要 WordPress 到 Astro 的转变:

  • PHP 模板变成 Astro 组件:服务器代码加 HTML,具有明确的文件组织。
  • 模板标签变成 props 和导入:数据通过参数而不是全局变量流动。
  • 主题文件变成 pages 目录:URL 与文件结构匹配。
  • 钩子变成插槽和中间件:插入点在传递内容的地方定义。
  • jQuery 在 WordPress 中默认加载;Astro 在您添加之前不发送 JavaScript。

Getting Started 指南开始构建您的第一个 EmDash 站点,或探索 Working with Content 以了解如何查询和渲染 CMS 数据。