Astro 開発者のための EmDash

このページ

EmDash は、管理アプリケーション、データベースを基盤とするコレクション、メディア、メニュー、タクソノミー、設定、リビジョン、プレビューを Astro サイトに追加します。ページとコンポーネントは、これまでどおり通常の Astro ファイルのままです。

EmDash が追加するもの

機能提供されるもの
管理画面/_emdash/admin で、ブラウザーからコレクション、メディア、メニュー、タクソノミー、設定を管理
データベースコレクションリクエスト時にクエリされる、エディターが管理するコンテンツ
メディアライブラリ保存された画像とファイル、およびテンプレート向けのメディアフィールド値
下書き、リビジョン、プレビュー公開前の編集作業
メニューとウィジェットエリアエントリのフィールド外にある、順序付けられた編集可能なサイト領域
サイト設定タイトル、タグライン、ロゴ、ページネーションのサイズなど、共有されるアイデンティティと表示の値
プラグインフック、ルート、ストレージ、オプションの管理画面拡張

これらの機能は Astro を置き換えるのではなく、Astro と並んで存在します。ルーティング、レイアウト、レンダリング、スタイル、デプロイアダプターは、引き続き Astro が制御します。

EmDash と Astro コレクション

Astro のコンテンツコレクションと EmDash のコレクションは共存できます。リポジトリが所有するコンテンツには Astro コレクションを、/_emdash/admin で管理するコンテンツには EmDash を使用します。

Astro コンテンツコレクションEmDash コレクション
ストレージプロジェクト内のファイルSQL データベース
編集リポジトリのワークフローEmDash 管理画面
クエリgetCollection()getEmDashCollection()
リッチテキストMarkdown または MDXPortable 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 は、ファイルベースのエントリを自身のデータベースにコピーしません。

サイトを構成する

現在の 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.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);
---

<article>
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

返される entry.id は Astro のルート識別子で、通常はスラグです。データベースのコンテンツ ID は entry.data.id です。保存済みのコンテンツ ID を必要とするヘルパーには、data.id を使用してください。

動的 CMS 機能を使用する

EmDash は、単一のコレクションエントリに属さないデータのためのサーバーヘルパーをエクスポートしています。

---
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() の例を、サンドボックス化されたパッケージにコピーしないでください。

次のステップ