Astro開発者のためのEmDash
このページで分かること
- EmDashがAstroのサイトに加える機能(管理画面、データベースのコレクション、メディアライブラリ、下書き・リビジョン・プレビュー、メニュー、ウィジェットエリア、サイト設定、プラグイン)
- Astroのコンテンツコレクションとの違いと、両方を1つのサイトで併用する方法
- 必要な設定(
output: "server"、EmDashインテグレーション、_emdashライブコレクション)と、コレクション・単一エントリーの取得方法
このページの目次
EmDashは、Astroのサイトに、管理アプリケーション、データベースに保存するコレクション、メディア、メニュー、タクソノミー、設定、リビジョン、プレビューを追加します。ページとコンポーネントは、通常のAstroのファイルのままです。
EmDashが追加するもの
| 機能 | 提供するもの |
|---|---|
| 管理画面 | /_emdash/admin で、コレクション、メディア、メニュー、タクソノミー、設定をブラウザーから管理する機能 |
| データベースのコレクション | 編集者が管理し、リクエスト時に取得するコンテンツ |
| メディアライブラリ | 保存された画像やファイルと、テンプレートで使うメディアフィールドの値 |
| 下書き、リビジョン、プレビュー | 公開前の編集作業 |
| メニューとウィジェットエリア | エントリーのフィールドとは別に、順序を持ち編集できるサイトの領域 |
| サイト設定 | タイトル、キャッチフレーズ、ロゴ、1ページあたりの件数など、サイト共通の基本情報と表示用の値 |
| プラグイン | フック、ルート、ストレージ、必要に応じた管理画面の拡張 |
これらの機能は、Astroを置き換えるのではなく、Astroと並んで動きます。ルーティング、レイアウト、描画、スタイル、デプロイ用のアダプターは、引き続きAstroが受け持ちます。
EmDashのコレクションとAstroのコレクション
Astroのコンテンツコレクションと、EmDashのコレクションは共存できます。リポジトリで管理するコンテンツにはAstroのコレクションを、/_emdash/admin で管理するコンテンツにはEmDashを使います。
| Astroのコンテンツコレクション | EmDashのコレクション | |
|---|---|---|
| 保存先 | プロジェクト内のファイル | SQLデータベース |
| 編集 | リポジトリのワークフロー | EmDashの管理画面 |
| クエリ | getCollection() |
getEmDashCollection() |
| リッチテキスト | MarkdownまたはMDX | Portable Text |
| 配信 | ビルド時のローダーまたはライブローダー | 実行時のライブローダー |
管理する人が異なる場合は、両方のコレクションの仕組みを使います。たとえば製品サイトでは、開発者が書くリリースノートをAstroのコンテンツコレクションに置き、編集者が書く記事をEmDashに置けます。
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
const [releaseNotes, { entries: articles }] = await Promise.all([
getCollection("releases"),
getEmDashCollection("articles", { limit: 3 }),
]);
---
2つの結果は別々のままです。EmDashは、ファイルで管理するエントリーを自分のデータベースにコピーしません。
やさしい解説
WordPressでいえば、EmDashのコレクションは管理画面で編集する投稿タイプにあたります。一方、Astroのコンテンツコレクションは、リポジトリの中のMarkdownファイルなどで管理するコンテンツです。1つのサイトで両方を使えるため、開発者が書くもの(リリースノートなど)はファイルで、編集者が書くもの(記事など)はEmDashで、と分けられます。2つは別々に取得し、EmDashがファイルの内容をデータベースに取り込むことはありません。
サイトの設定
現在のNode向けのテンプレートは、Astroをサーバー出力に設定し、EmDashのインテグレーションを追加して、SQLiteとローカルストレージのアダプターを使います。
次の最小限の設定には、必要な部分が含まれています。
import node from "@astrojs/node";
import react from "@astrojs/react";
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
integrations: [
react(),
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
EmDashには、D1とR2向けに設定されたCloudflare用のテンプレートもあります。Node用のアダプターを手で書き換えるのではなく、デプロイ先に合ったテンプレートから始めます。
ライブコレクションの登録
テンプレートは、_emdash という名前の1つのAstroのライブコレクションを通して、EmDashのコンテンツを公開します。
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};
getEmDashCollection() と getEmDashEntry() は、このローダーを通して、指定したコンテンツタイプを選びます。
コレクションの取得
次のクエリは、最近公開された投稿を読み込みます。orderBy には保存されているフィールド名を使い、それぞれの名前に "asc" または "desc" を指定します。
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 10,
});
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>
))}
未ログインのクエリは、公開済みのコンテンツを返します。明示的な status の絞り込みは、認証済みのコードやプレビューに対応するコードで役立ちます。where には、コンテンツのフィールドとタクソノミーの名前を指定できます。絞り込みとページ分割の完全な形については、コンテンツの取得を参照します。
1件のエントリーの取得
getEmDashEntry() には、スラッグまたはデータベースのIDを渡します。次のルートでは、URLのスラッグを使います。
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
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);
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
返される entry.id はAstroのルートの識別子で、通常はスラッグです。データベース上のコンテンツのIDは entry.data.id です。保存されたコンテンツのIDを必要とするヘルパーには、data.id を使います。
CMSの動的な機能の利用
EmDashは、1つのコレクションのエントリーに属さないデータのために、サーバー用のヘルパーをエクスポートしています。
---
import { getMenu, getSiteSettings } from "emdash";
import { WidgetArea } from "emdash/ui";
const [menu, settings] = await Promise.all([
getMenu("primary"),
getSiteSettings(),
]);
---
<header>
<a href="/">{settings.title}</a>
<nav>
{menu?.items.map((item) => <a href={item.url}>{item.label}</a>)}
</nav>
</header>
<main><slot /></main>
<aside><WidgetArea name="sidebar" /></aside>
プラグイン形式の選択
サンドボックス型プラグインとネイティブ型プラグインでは、パッケージの形が異なります。サンドボックス型プラグインは、emdash-plugin.jsonc と、デフォルトエクスポートした src/plugin.ts のオブジェクトを使います。ネイティブ型プラグインは、記述子のファクトリーと、definePlugin() で作った createPlugin() をエクスポートします。
プラグインを追加する前に、プラグイン形式の選び方を読みます。ネイティブ型の definePlugin() の例を、サンドボックス型のパッケージにコピーしないでください。
次のステップ
- コンテンツを取得する:絞り込み、ページ分割、ロケール、キャッシュのヒントを使います。
- テーマを作成する:Astroのサイトとそのシードファイルを、再利用できるテンプレートとしてまとめます。