面向 WordPress 开发者的 Astro

本页内容

Astro 为 EmDash 站点提供页面、布局、组件和服务器渲染。本指南涵盖当前 EmDash 模板使用的 Astro 概念。假设你已经了解 WordPress 主题和 PHP 模板。

对于非 EmDash 特有的框架功能,请使用 Astro 文档。

项目结构

Astro 站点为每种文件类型分配一个明确的目录。当前的 EmDash 模板使用以下结构:

WordPressAstro用途
index.php、single.php、page.phpsrc/pages/URL 路由
template-parts/src/components/可复用标记
header.php 和 footer.phpsrc/layouts/共享页面外壳
style.csssrc/styles/站点样式
插件和数据库设置astro.config.mjs集成和服务器适配器
主题设置数据seed/seed.json集合、菜单和示例内容

博客模板使用与其公开 URL 匹配的路由目录:

src/
├── components/
│   └── PostCard.astro
├── layouts/
│   └── Base.astro
├── pages/
│   ├── index.astro
│   ├── pages/
│   │   └── [slug].astro
│   └── posts/
│       ├── index.astro
│       └── [slug].astro
└── live.config.ts

Astro 组件

.astro 组件将服务器端 TypeScript 和 HTML 模板结合在一起。--- 围栏之间的代码在服务器上运行。第二个围栏下方的标记成为响应 HTML。

以下组件在其 frontmatter 中声明 props,并在模板中渲染它们:

---
interface Props {
  title: string;
  excerpt?: string;
  href: string;
}

const { title, excerpt, href } = Astro.props;
---

<article>
  <h2><a href={href}>{title}</a></h2>
  {excerpt && <p>{excerpt}</p>}
</article>

Astro 会对使用 {value} 渲染的值进行转义。导入、数据库查询和其他服务器操作属于 frontmatter。

模板表达式

Astro 模板在 PHP 模板会切换到 <?php ?> 的位置使用花括号。EmDash 模板中最常见的模式是值、条件和数组映射:

目的Astro 语法
输出值{post.data.title}
值存在时渲染{post.data.excerpt && <p>{post.data.excerpt}</p>}
在两个结果间选择{posts.length === 0 ? <p>暂无文章。</p> : <PostList />}
渲染列表{posts.map((post) => <PostCard title={post.data.title} excerpt={post.data.excerpt} href={"/posts/" + post.id} />)}

表达式可以使用 frontmatter 中准备的变量、Astro.props 中的值或 EmDash 查询返回的数据。Astro 默认会转义字符串值;使用 <PortableText /> 等渲染器来处理结构化富文本,而不是注入 HTML。

Props 和插槽

Props 类似于传递给 get_template_part() 的 $args。它们使每个输入明确,并可以由 TypeScript 进行检查。

插槽允许父组件向子组件传递标记。默认插槽适用于页面内容,而命名插槽提供额外的插入点:

---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<article>
  <h2>{title}</h2>
  <slot />
  <footer><slot name="footer" /></footer>
</article>

以下页面填充了两个插槽:

---
import Card from "../components/Card.astro";
---

<Card title="最新文章">
  <p>卡片的主要内容。</p>
  <a slot="footer" href="/posts/latest">阅读文章</a>
</Card>

插槽对于组件调用是局部的。它们不像 WordPress 的 action 那样可以接收在其他地方注册的回调。

布局

布局拥有 WordPress 主题通常在 header.php 和 footer.php 之间分割的共享文档结构。页面导入布局并通过其插槽传递内容。

以下布局提供了一个文档外壳:

---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
  </head>
  <body>
    <header><a href="/">我的站点</a></header>
    <main><slot /></main>
  </body>
</html>

以下页面提供布局的标题和主要内容:

---
import Base from "../layouts/Base.astro";
---

<Base title="首页">
  <h1>最新文章</h1>
</Base>

基于文件的路由

src/pages/ 中的文件定义路由。方括号文件名创建动态段。

文件URL
src/pages/index.astro/
src/pages/posts/index.astro/posts
src/pages/posts/[slug].astro/posts/hello-world
src/pages/pages/[slug].astro/pages/about

在 src/pages/posts/[slug].astro 内部,Astro.params.slug 包含 URL 中的值。有关剩余参数、重定向和其他路由功能,请阅读 Astro 路由。

服务器渲染

当前的 EmDash 模板在 astro.config.mjs 中使用 output: "server"。因此页面可以在每次请求时查询数据库,已发布的内容不依赖于新的静态构建。

除非站点刻意将 EmDash 作为构建时数据源,否则不要向 EmDash 主题路由添加 getStaticPaths()。提供的主题是服务器渲染的。

有关框架级行为,请阅读 Astro 按需渲染。

查询 EmDash 内容

EmDash 使用 getEmDashCollection() 和 getEmDashEntry() 包装 Astro 实时内容集合。集合结果包含一个 entries 数组。单条目结果包含 entry,当没有已发布的条目匹配时为 null。

以下存档使用与当前博客模板相同的排序和标识符:

---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";

const { entries: posts, error } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
});

if (error) {
  return new Response("无法加载文章", { status: 500 });
}
---

<Base title="文章">
  {posts.map((post) => (
    <article>
      <h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
      {post.data.excerpt && <p>{post.data.excerpt}</p>}
    </article>
  ))}
</Base>

post.id 是 Astro 公开的路由标识符,通常是条目的 slug。post.data.id 是数据库标识符。当 API 期望存储的内容 ID 时(如分类法或评论辅助函数),请使用 data.id。

以下动态路由按 URL 中的 slug 查找文章并渲染其 Portable Text 字段:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");

const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("无法加载文章", { status: 500 });
if (!post) return Astro.redirect("/404");
---

<Base title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <PortableText value={post.data.content} />
  </article>
</Base>

继续学习 Astro

EmDash 模板还使用组件样式和小型浏览器脚本,但这些是普通的 Astro 功能而非 EmDash 概念。有关作用域和全局样式,请阅读样式和 CSS;当组件需要浏览器端行为时,请阅读脚本和事件处理。