WordPress 主題可以有系統地轉換為 EmDash。視覺設計、內容結構和動態功能都可以透過三階段方法進行遷移。
三階段方法
-
設計提取
從 WordPress 主題中提取 CSS 變數、字型、色彩和版面模式。分析線上網站以擷取計算樣式和響應式中斷點。
-
範本轉換
將 PHP 範本轉換為 Astro 元件。將 WordPress 範本層級對應到 Astro 路由,並將範本標籤轉換為 EmDash API 呼叫。
-
動態功能
將導覽選單、小工具區域、分類法和網站設定移植到對應的 EmDash 功能。建立種子檔案以擷取完整的內容模型。
第一階段:設計提取
定位 CSS 與設計語彙
| 檔案 | 用途 |
|---|---|
style.css | 含主題標頭的主要樣式表 |
assets/css/ | 額外樣式表 |
theme.json | 區塊主題(WP 5.9+)— 結構化語彙 |
提取設計語彙
| WordPress 模式 | EmDash 變數 |
|---|---|
| Body 字型系列 | --font-body |
| 標題字型 | --font-heading |
| 主色 | --color-primary |
| 背景 | --color-base |
| 文字顏色 | --color-contrast |
| 內容寬度 | --content-width |
建立基礎版面
建立 src/layouts/Base.astro,包含提取的 CSS 變數、頁首/頁尾結構、字型載入和響應式中斷點:
---
import { getSiteSettings, getMenu } from "emdash";
import "../styles/global.css";
const { title, description } = Astro.props;
const settings = await getSiteSettings();
const primaryMenu = await getMenu("primary");
const pageTitle = title ? `${title} | ${settings.title}` : settings.title;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{pageTitle}</title>
</head>
<body>
<header>
{settings.logo ? (
<img src={settings.logo.url} alt={settings.title} />
) : (
<span>{settings.title}</span>
)}
<nav>
{primaryMenu?.items.map((item) => (
<a href={item.url}>{item.label}</a>
))}
</nav>
</header>
<main><slot /></main>
</body>
</html>
第二階段:範本轉換
範本層級對應
| WordPress 範本 | Astro 路由 |
|---|---|
index.php | src/pages/index.astro |
single.php | src/pages/posts/[slug].astro |
single-{post_type}.php | src/pages/{type}/[slug].astro |
page.php | src/pages/[...slug].astro |
archive.php | src/pages/posts/index.astro |
category.php | src/pages/categories/[slug].astro |
tag.php | src/pages/tags/[slug].astro |
search.php | src/pages/search.astro |
404.php | src/pages/404.astro |
header.php / footer.php | src/layouts/Base.astro 的一部分 |
sidebar.php | src/components/Sidebar.astro |
範本標籤對應
| WordPress 函式 | EmDash 對應 |
|---|---|
have_posts() / the_post() | getEmDashCollection() |
get_post() | getEmDashEntry() |
the_title() | post.data.title |
the_content() | <PortableText value={post.data.content} /> |
the_excerpt() | post.data.excerpt |
the_permalink() | /posts/${post.slug} |
the_post_thumbnail() | post.data.featured_image |
get_the_date() | post.data.publishedAt |
get_the_category() | getEntryTerms(coll, id, "categories") |
get_the_tags() | getEntryTerms(coll, id, "tags") |
轉換 The Loop
WordPress
<?php while (have_posts()) : the_post(); ?>
<article>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?> EmDash
---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";
const { entries: posts } = await getEmDashCollection("posts", {
where: { status: "published" },
orderBy: { publishedAt: "desc" },
});
---
<Base title="Blog">
{posts.map((post) => (
<article>
<h2><a href={`/posts/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.excerpt}</p>
</article>
))}
</Base> 轉換單篇範本
WordPress
<?php get_header(); ?>
<article>
<h1><?php the_title(); ?></h1>
<?php the_content(); ?>
<div class="post-meta">
Posted in: <?php the_category(', '); ?>
</div>
</article>
<?php get_footer(); ?> EmDash
---
import { getEmDashCollection, getEntryTerms } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts");
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const categories = await getEntryTerms("posts", post.id, "categories");
---
<Base title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
<div class="post-meta">
Posted in: {categories.map((cat) => (
<a href={`/categories/${cat.slug}`}>{cat.label}</a>
))}
</div>
</article>
</Base> 轉換範本片段
WordPress 的 get_template_part() 呼叫轉為 Astro 元件匯入。template-parts/content-post.php 部分範本變為 PostCard.astro 元件,你在迴圈中匯入並渲染。
第三階段:動態功能
導覽選單
在 functions.php 中識別選單,並建立對應的 EmDash 選單:
---
import { getMenu } from "emdash";
const menu = await getMenu("primary");
---
{menu && (
<nav class="primary-nav">
<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 class="submenu">
{item.children.map((child) => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
)}
小工具區域(側邊欄)
識別主題中的小工具區域並渲染它們:
---
import { getWidgetArea, getMenu } from "emdash";
import { PortableText } from "emdash/ui";
import RecentPosts from "./widgets/RecentPosts.astro";
const sidebar = await getWidgetArea("sidebar");
const widgetComponents = { "core:recent-posts": RecentPosts };
---
{sidebar && sidebar.widgets.length > 0 && (
<aside class="sidebar">
{sidebar.widgets.map(async (widget) => (
<div class="widget">
{widget.title && <h3>{widget.title}</h3>}
{widget.type === "content" && <PortableText value={widget.content} />}
{widget.type === "menu" && (
<nav>
{await getMenu(widget.menuName).then((m) =>
m?.items.map((item) => <a href={item.url}>{item.label}</a>)
)}
</nav>
)}
{widget.type === "component" && widgetComponents[widget.componentId] && (
<Fragment>
{(() => {
const Component = widgetComponents[widget.componentId];
return <Component {...widget.componentProps} />;
})()}
</Fragment>
)}
</div>
))}
</aside>
)}
小工具類型對應
| WordPress 小工具 | EmDash 小工具類型 |
|---|---|
| 文字/自訂 HTML | type: "content" |
| 自訂選單 | type: "menu" |
| 近期文章 | component: "core:recent-posts" |
| 分類 | component: "core:categories" |
| 標籤雲 | component: "core:tag-cloud" |
| 搜尋 | component: "core:search" |
分類法
查詢主題中註冊的分類法:
---
import { getTaxonomyTerms, getEntriesByTerm } from "emdash";
import Base from "../../layouts/Base.astro";
export async function getStaticPaths() {
const genres = await getTaxonomyTerms("genre");
return genres.map((genre) => ({
params: { slug: genre.slug },
props: { genre },
}));
}
const { genre } = Astro.props;
const books = await getEntriesByTerm("books", "genre", genre.slug);
---
<Base title={genre.label}>
<h1>{genre.label}</h1>
{books.map((book) => (
<article>
<h2><a href={`/books/${book.slug}`}>{book.data.title}</a></h2>
</article>
))}
</Base>
網站設定對應
| WordPress 自訂工具 | EmDash 設定 |
|---|---|
| 網站標題 | title |
| 標語 | tagline |
| 網站圖示 | favicon |
| 自訂標誌 | logo |
| 每頁文章數 | postsPerPage |
短碼到 Portable Text
WordPress 短碼轉為 Portable Text 自訂區塊:
WordPress
add_shortcode('gallery', function($atts) {
$ids = explode(',', $atts['ids']);
return '<div class="gallery">...</div>';
}); EmDash
---
const { images } = Astro.props;
---
<div class="gallery">
{images.map((img) => (
<img src={img.url} alt={img.alt || ""} loading="lazy" />
))}
</div>註冊到 PortableText:
<PortableText value={content} components={{ gallery: Gallery }} /> 種子檔案結構
在種子檔案中擷取完整的內容模型。包含設定、分類法、選單和小工具區域:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": { "name": "Ported Theme" },
"settings": { "title": "My Site", "tagline": "Welcome", "postsPerPage": 10 },
"taxonomies": [
{
"name": "category",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"]
}
],
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "custom", "label": "Blog", "url": "/posts" }
]
}
],
"widgetAreas": [
{
"name": "sidebar",
"label": "Main Sidebar",
"widgets": [
{
"type": "component",
"componentId": "core:recent-posts",
"props": { "count": 5 }
}
]
}
]
}
完整規範見 種子檔案格式。
移植檢查清單
第一階段(設計): CSS 變數已提取、字型載入中、色彩方案一致、響應式中斷點正常運作。
第二階段(範本): 首頁、單篇文章、封存頁和 404 頁面都正確渲染。
第三階段(動態): 網站設定已設定、選單可運作、分類法可查詢、小工具區域渲染中、種子檔案完成。
邊緣情況
子主題
如果主題有父主題(檢查 style.css 中的 Template:),先分析父主題,再套用子主題覆寫。
區塊主題(FSE)
WordPress 5.9+ 區塊主題使用 theme.json 定義設計語彙,使用 templates/*.html 定義區塊標記。將區塊標記轉換為 Astro 元件,並從 theme.json 提取語彙。
頁面編輯器
使用 Elementor、Divi 或類似工具建立的內容儲存在文章 meta 中,而非主題檔案中。這類內容透過 WXR 匯入,而非主題移植。主題移植專注於外殼——頁面編輯器內容在匯入後透過 Portable Text 渲染。
後續步驟
- 建立主題 — 建立可發布的 EmDash 主題
- 種子檔案格式 — 完整的種子檔案規範
- 從 WordPress 遷移 — 匯入 WordPress 內容