移植 WordPress 主題

本頁內容

移植 WordPress 主題時,請將它的設計、路由範本和由資料庫管理的功能分開處理。最終得到的是一個包含 EmDash 查詢和種子檔案的完整 Astro 專案,而不是由主題執行階段載入的 PHP 主題。

從內容和 URL 開始

在轉換範本之前,先匯入具代表性的內容。記錄站台使用的 WordPress 文章類型、分類法、固定連結規則、選單、小工具區域、Customizer 值、短代碼,以及由外掛程式產生的標記。

為每種內容類型選定新的 URL。Astro 的路由是明確的檔案,因此沒有與 WordPress 範本階層對應的自動機制。

目前的部落格範本使用如下對應:

WordPressEmDash 範本路徑
front-page.php 或 home.phpsrc/pages/index.astro
single.phpsrc/pages/posts/[slug].astro
archive.phpsrc/pages/posts/index.astro
page.phpsrc/pages/pages/[slug].astro
category.phpsrc/pages/category/[slug].astro
tag.phpsrc/pages/tag/[slug].astro
search.phpsrc/pages/search.astro
404.phpsrc/pages/404.astro
header.php 和 footer.phpsrc/layouts/Base.astro
template-parts/content.phpsrc/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 檔案。

分階段移植並逐步驗證

  1. 匯入具代表性的 WordPress 匯出檔案,並記錄所有不受支援的區塊或欄位。

  2. 從適用於部署目標的目前 EmDash 範本開始。

  3. 實作共用版面配置、設計權杖、排版和響應式外殼。

  4. 使用匯入後的集合 slug 和欄位,實作封存路由和單一條目路由。

  5. 新增來源主題實際用到的分類法路由、搜尋、選單、小工具區域和站台設定。

  6. 為每個路由相依項目新增種子定義,並在空的資料庫上測試設定流程。

  7. 在行動裝置和桌面寬度下比較具代表性的公開 URL。測試空欄位、過長的標題、缺失的圖片、草稿和 404 路由。

  8. 在切換上線之前,為每個變更過的固定連結準備重新導向。

應對棘手的 WordPress 主題

  • 子主題: 合併解析後的父主題和子主題的輸出。移植的是實際生效的範本和樣式,而不僅僅是子主題目錄。
  • 區塊主題: 將 theme.json 作為設計權杖的來源,將 templates/*.html 作為內容結構的來源,然後用 Astro 元件來表達結果。
  • 頁面建構器: 先匯入具代表性的頁面。建構器的短代碼和專有 JSON 通常需要經過審慎的 Portable Text 轉換,或者重新設計頁面範本。
  • WooCommerce 主題: 將商務行為視為獨立的應用程式整合。移植呈現層檔案並不能取代產品、購物車、結帳、付款或訂單的語意。