WordPressテーマの移植
このページで分かること
- 移植の前に記録すること(投稿タイプ、タクソノミー、パーマリンク、メニュー、ウィジェットなど)と、WordPressのテンプレートファイルとAstroのルートの対応
- デザインの取り出し方と、一覧・個別記事・タクソノミーのアーカイブ、メニュー・ウィジェットエリア・サイト情報・ページテンプレート・ショートコードの変換
- シードファイルの作成、段階的な移植の手順、子テーマ・ブロックテーマ・ページビルダー・WooCommerceのテーマの扱い
このページの目次
WordPressのテーマは、デザイン、ルートのテンプレート、データベースで管理する機能の3つに分けて移植します。移植の結果は、EmDashの取得処理とシードファイルを備えた完全なAstroプロジェクトになります。テーマの実行環境が読み込むPHPのテーマにはなりません。
コンテンツとURLから始める
テンプレートを変換する前に、代表的なコンテンツをインポートします。サイトで使っているWordPressの投稿タイプ、タクソノミー、パーマリンクのルール、メニュー、ウィジェットエリア、カスタマイザーの値、ショートコード、プラグインが生成するマークアップを記録します。
コンテンツの種類ごとに、新しいURLを決めます。Astroのルートは明示的なファイルで決まるため、WordPressのテンプレート階層に自動で対応するものはありません。
現在のblogテンプレートは、次の対応を使っています。
| 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 または別のコンポーネント |
starterテンプレートは、固定ページに src/pages/[slug].astro を使っています。ルートの構成をどちらか1つに決め、シードの urlPattern とリダイレクトをそれに合わせます。
やさしい解説
WordPressでは、single.php や archive.php のようなファイル名の決まり(テンプレート階層)に従って、どのファイルでページを描画するかが自動で決まります。Astroにはこの仕組みがなく、src/pages/ の下に置いたファイルの場所がそのままURLになります。そのため、移植の最初に「記事は /posts/スラッグ にする」のようにURLを決め、上の表を参考に対応するファイルを作ります。決めたURLは、シードファイルの urlPattern と、古いURLからのリダイレクトにも同じものを使います。
デザインを取り出す
描画されるデザインを決めているソースファイルを集めます。
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>
))}
sort や sortBy ではなく、orderBy を使います。キーには、published_at、created_at のような保存されているフィールド名や、インデックスを持つコレクションのフィールドを指定します。
やさしい解説
WordPressの WP_Query で「投稿を日付の新しい順に12件」と指定していた部分は、EmDashでは getEmDashCollection("posts", { orderBy: ..., limit: 12 }) と書きます。while ループで1件ずつ取り出す代わりに、受け取った posts の配列を map で順に描画します。取得に失敗した場合は error に値が入るため、例ではステータス500の応答を返しています。
個別エントリーのテンプレートを変換する
Astro.params から得たスラッグを使って、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.redirect("/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.redirect("/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のルート識別子で、通常はスラッグです。post.data.id は、EmDashに保存されているコンテンツIDです。コメント、タクソノミーのヘルパー、ビジュアル編集のコンテキストには data.id を使います。
タクソノミーのアーカイブを変換する
タクソノミー名を指定してタームを取得し、そのタクソノミー名をコレクションの where フィルターに使います。現在のblogテンプレートは、単数形のタクソノミー名とルートのディレクトリー名を使っています。
---
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.redirect("/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>)}
アーカイブで多数のエントリーのタームが必要な場合は、entry.data.id を指定して getTermsForEntries() を使います。これにより、エントリーごとのヘルパーをループの中で呼び出す代わりに、関連を取得するクエリを1回にまとめられます。
テーマの動的な機能を変換する
メニュー
メニューの定義を 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 |
| 1ページに表示する最大投稿数 | postsPerPage |
ページテンプレート
編集者が複数のページレイアウトから選ぶ必要がある場合は、pagesコレクションにセレクトフィールドを追加し、保存される値ごとに既知のコンポーネントを対応させます。保存された文字列を、任意のインポートパスに変換しないでください。
ショートコードとブロック
コンテンツを含むショートコードとWordPressのブロックは、Portable Textの形に対応させます。組み込みのブロックには通常の PortableText レンダラーを使い、独自の _type の値にはコンポーネントマップを使います。
シードは独自のブロックのデータを用意できますが、独自のエディターは登録しません。移植にパッケージ化したAstroのレンダラーや、独自のReactの編集UIが必要な場合は、ネイティブ型プラグインを使います。
たとえば、WordPressのギャラリーのショートコードを publication.gallery のような名前空間付きの _type に対応させ、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 }} />
やさしい解説
WordPressでは、メニュー、ウィジェット、サイトのタイトルやロゴを管理画面で変更でき、テーマはそれを読み込んで表示します。EmDashでも同じで、テンプレートにリンクや文言を直接書かず、getMenu()、<WidgetArea>、getSiteSettings() で管理画面の設定を読み込みます。記事の本文中のショートコード(たとえばギャラリー)は、Portable Textの独自の _type として保存し、その _type を描画するAstroのコンポーネントを自分で用意します。
シードを作る
現在のテンプレートでは、シードは 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、参照、多言語化、メディア、検証、適用のオプションについては、シードファイルを参照してください。
確かめられる段階に分けて移植する
-
WordPressの代表的なエクスポートをインポートし、対応していないブロックやフィールドを記録します。
-
デプロイ先に合った、現在のEmDashのテンプレートから始めます。
-
共通のレイアウト、トークン、タイポグラフィー、レスポンシブの枠組みを実装します。
-
インポートしたコレクションのスラッグとフィールドを使って、アーカイブと個別エントリーのルートを実装します。
-
移植元のテーマが実際に使っているタクソノミーのルート、検索、メニュー、ウィジェットエリア、サイトの設定を追加します。
-
ルートが依存するものすべてのシードの定義を追加し、空のデータベースに対して初期設定をテストします。
-
代表的な公開URLを、モバイルとデスクトップの幅で比較します。空のフィールド、長いタイトル、画像がない場合、下書き、404のルートをテストします。
-
切り替えの前に、変更したすべてのパーマリンクのリダイレクトを用意します。
移植が難しいWordPressテーマの扱い
- 子テーマ:親テーマと子テーマを解決した結果の出力を合わせて扱います。子テーマのディレクトリーだけでなく、実際に使われるテンプレートとスタイルを移植します。
- ブロックテーマ:
theme.jsonをデザイントークンの元として、templates/*.htmlをコンテンツの構造として使い、その結果をAstroのコンポーネントとして表現します。 - ページビルダー:先に代表的なページをインポートします。ビルダーのショートコードや独自形式のJSONは、通常、意図的にPortable Textへ変換するか、ページテンプレートを設計し直す必要があります。
- WooCommerceのテーマ:ECの動作は、別のアプリケーションとの連携として扱います。表示用のファイルを移植しても、商品、カート、チェックアウト、決済、注文の仕組みの代わりにはなりません。