Astro 为 EmDash 站点提供页面、布局、组件和服务端渲染。本指南介绍当前 EmDash 模板所使用的 Astro 概念,并假定你已经了解 WordPress 主题和 PHP 模板。
对于并非 EmDash 特有的框架功能,请参阅 Astro 文档。
项目结构
Astro 站点为每种文件指定了明确的目录。当前的 EmDash 模板使用如下结构:
| WordPress | Astro | 用途 |
|---|---|---|
index.php、single.php、page.php | src/pages/ | URL 路由 |
template-parts/ | src/components/ | 可复用的标记 |
header.php 和 footer.php | src/layouts/ | 共享的页面外壳 |
style.css | src/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>No posts yet.</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="Latest post">
<p>The main card content.</p>
<a slot="footer" href="/posts/latest">Read the post</a>
</Card>
插槽只作用于该组件调用的局部范围。它们的行为不像 WordPress 的 action,后者可以接收在其他位置注册的回调。
布局
布局负责共享的文档结构,WordPress 主题通常会把这部分拆分到 header.php 和 footer.php 中。页面导入布局,并通过布局的插槽传入自己的内容。
下面的布局提供了文档外壳:
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
</head>
<body>
<header><a href="/">My site</a></header>
<main><slot /></main>
</body>
</html>
下面的页面提供了布局的标题和主要内容:
---
import Base from "../layouts/Base.astro";
---
<Base title="Home">
<h1>Latest posts</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("Could not load posts", { status: 500 });
}
---
<Base title="Posts">
{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.rewrite("/404");
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.rewrite("/404");
---
<Base title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
</Base>
继续学习 Astro
EmDash 模板还使用组件样式和小型浏览器脚本,但它们是普通的 Astro 功能,而不是 EmDash 的概念。有关作用域样式和全局样式,请阅读样式与 CSS;当组件需要浏览器端行为时,请阅读脚本与事件处理。