移植 WordPress 主题时,请将它的设计、路由模板和由数据库管理的功能分开处理。最终得到的是一个包含 EmDash 查询和种子文件的完整 Astro 项目,而不是由主题运行时加载的 PHP 主题。
从内容和 URL 开始
在转换模板之前,先导入具有代表性的内容。记录站点使用的 WordPress 文章类型、分类法、固定链接规则、菜单、小部件区域、Customizer 值、短代码,以及由插件生成的标记。
为每种内容类型选定新的 URL。Astro 的路由是显式的文件,因此没有与 WordPress 模板层级对应的自动机制。
当前的博客模板使用如下映射:
| WordPress | EmDash 模板路径 |
|---|---|
front-page.php 或 home.php | src/pages/index.astro |
single.php | src/pages/posts/[slug].astro |
archive.php | src/pages/posts/index.astro |
page.php | src/pages/pages/[slug].astro |
category.php | src/pages/category/[slug].astro |
tag.php | src/pages/tag/[slug].astro |
search.php | src/pages/search.astro |
404.php | src/pages/404.astro |
header.php 和 footer.php | src/layouts/Base.astro |
template-parts/content.php | src/components/PostCard.astro 或其他组件 |
入门模板则使用 src/pages/[slug].astro 来承载页面。请选定一种路由模型,并让种子文件中的 urlPattern 和重定向与之保持一致。
提取设计
收集决定最终渲染设计的源文件:
style.css以及通过 enqueue 加载的样式表。- 区块主题的
theme.json。 - 模板标记和模板部件。
- 字体、图标和图片,以及它们的许可证。
- 响应式断点和交互行为。
将设计令牌复制到 Astro 项目的 CSS 中,并在 src/layouts/Base.astro 中构建共享的文档外壳。在展开各个路由之前,先把重复出现的模板部件转换为组件。
当新组件不需要 WordPress 生成的类名或 JavaScript 所提供的行为时,不要照搬它们。要保留的是可观察的布局与交互,而不是实现层面的残留。
转换归档查询
WP_Query 变为 getEmDashCollection()。请在查询中完成过滤和排序,让数据库来做这些工作。
下面的示例渲染一个最新文章归档:
WordPress
<?php
$posts = new WP_Query([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 12,
'orderby' => 'date',
'order' => 'DESC',
]);
while ($posts->have_posts()) :
$posts->the_post();
?>
<article>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
</article>
<?php endwhile; wp_reset_postdata(); ?> EmDash
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 12,
});
if (error) return new Response("Could not load posts", { status: 500 });
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
{posts.map((post) => (
<article>
<h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
</article>
))} 请使用 orderBy,而不是 sort 或 sortBy。键是存储的字段名,例如 published_at、created_at,或已建立索引的集合字段。
转换单条目模板
使用 Astro.params 中的 slug 调用 getEmDashEntry()。用 PortableText 渲染富文本。
---
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, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.rewrite("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<Base title={post.data.title} content={{ collection: "posts", id: post.data.id, slug }}>
<article>
<h1 {...post.edit.title}>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
</Base>
post.id 是 Astro 的路由标识符,通常就是 slug。post.data.id 是存储的 EmDash 内容 ID。评论、分类法辅助函数和可视化编辑上下文请使用 data.id。
转换分类法归档
先用分类法名称解析术语,再在集合的 where 过滤条件中使用该名称。当前的博客模板使用单数形式的分类法名称和路由目录:
---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";
const slug = decodeSlug(Astro.params.slug);
const term = slug ? await getTerm("category", slug, { includeCounts: false }) : null;
if (!term) return Astro.rewrite("/404");
const { entries: posts } = await getEmDashCollection("posts", {
where: { category: term.slug },
orderBy: { published_at: "desc" },
});
---
<h1>{term.label}</h1>
{posts.map((post) => <a href={`/posts/${post.id}`}>{post.data.title}</a>)}
当归档页面需要为许多条目获取术语时,请将 getTermsForEntries() 与 entry.data.id 搭配使用。这样会批量执行关系查询,而不是在循环中为每个条目调用辅助函数。
转换动态主题功能
菜单
在 seed/seed.json 中创建菜单定义,并用 getMenu("primary") 查询。菜单项在设置完成后仍可重新排序和编辑,因此不要把同一份导航再以硬编码链接的形式重复写一遍。
下面的组件保留了嵌套的菜单项,并标出当前页面:
---
import { getMenu } from "emdash";
const menu = await getMenu("primary");
---
{menu && (
<nav aria-label="Primary navigation">
<ul>
{menu.items.map((item) => (
<li>
<a href={item.url} aria-current={Astro.url.pathname === item.url ? "page" : undefined}>
{item.label}
</a>
{item.children.length > 0 && (
<ul>
{item.children.map((child) => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
)}
小部件区域
在种子文件中定义小部件区域,并用 emdash/ui 中的 <WidgetArea name="sidebar" /> 渲染。只有当模板注册了对应的组件 ID 时,才使用组件小部件。
---
import { WidgetArea } from "emdash/ui";
---
<main><slot /></main>
<aside aria-label="Related content">
<WidgetArea name="sidebar" />
</aside>
站点标识
用 getSiteSettings() 读取站点标题、副标题、徽标和站点图标。静态的法律文案或设计文案可以留在模板中,但由管理员管理的站点标识必须来自设置。
| WordPress 值 | EmDash 设置 |
|---|---|
| 站点标题 | title |
| 副标题 | tagline |
| 自定义徽标 | logo |
| 站点图标 | favicon |
| 每页文章数 | postsPerPage |
页面模板
如果编辑者必须在多种页面布局中进行选择,请在页面集合中添加一个选择字段,并把每个存储的值映射到已知的组件。不要把存储的字符串变成任意的导入路径。
短代码和区块
将承载内容的短代码和 WordPress 区块映射为 Portable Text 的结构。内置区块使用普通的 PortableText 渲染器,自定义的 _type 值则使用组件映射。
种子文件可以提供自定义区块数据,但它不会注册自定义编辑器。如果移植需要打包的 Astro 渲染器或自定义 React 编辑界面,请使用原生插件。
例如,把 WordPress 的图库短代码映射为带命名空间的 _type,如 publication.gallery,创建一个 Gallery.astro 渲染器,并通过 PortableText 组件映射传入:
---
import type { PortableTextBlock } from "emdash";
import { PortableText } from "emdash/ui";
import Gallery from "./blocks/Gallery.astro";
interface Props {
value: PortableTextBlock[];
}
const { value } = Astro.props;
const customTypes = { "publication.gallery": Gallery };
---
<PortableText value={value} components={{ type: customTypes }} />
构建种子文件
在当前模板中,种子文件位于 seed/seed.json,并由 package.json#emdash.seed 指向。请声明模板所依赖的每个集合字段、分类法、菜单、小部件区域、区段和示例条目。
下面的片段定义了文章面向路由的字段:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"collections": [
{
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"supports": ["drafts", "revisions", "search", "seo"],
"urlPattern": "/posts/{slug}",
"fields": [
{ "slug": "title", "label": "Title", "type": "string", "required": true },
{ "slug": "content", "label": "Content", "type": "portableText" },
{ "slug": "excerpt", "label": "Excerpt", "type": "text" },
{ "slug": "featured_image", "label": "Featured image", "type": "image" }
]
}
]
}
有关内容 ID、引用、本地化、媒体、校验和应用选项,请阅读 Seed 文件。
分阶段移植并逐步验证
-
导入具有代表性的 WordPress 导出文件,并记录所有不受支持的区块或字段。
-
从适用于部署目标的当前 EmDash 模板开始。
-
实现共享布局、设计令牌、排版和响应式外壳。
-
使用导入后的集合 slug 和字段,实现归档路由和单条目路由。
-
添加源主题实际用到的分类法路由、搜索、菜单、小部件区域和站点设置。
-
为每个路由依赖项添加种子定义,并在空数据库上测试设置流程。
-
在移动端和桌面端宽度下比较具有代表性的公开 URL。测试空字段、过长的标题、缺失的图片、草稿和 404 路由。
-
在切换上线之前,为每个发生变化的固定链接准备重定向。
应对棘手的 WordPress 主题
- 子主题: 合并解析后的父主题和子主题的输出。移植的是实际生效的模板和样式,而不仅仅是子主题目录。
- 区块主题: 将
theme.json作为设计令牌的来源,将templates/*.html作为内容结构的来源,然后用 Astro 组件来表达结果。 - 页面构建器: 先导入具有代表性的页面。构建器的短代码和专有 JSON 通常需要经过审慎的 Portable Text 转换,或者重新设计页面模板。
- WooCommerce 主题: 将商务行为视为独立的应用集成。移植表现层文件并不能取代产品、购物车、结账、支付或订单的语义。