コンテンツソース
ローカルファイル、リモートリポジトリ、または任意のカスタムバックエンドからドキュメントを取得し、複数のソースをビルド時に読み込む1つのスタティックファーストなサイトに統合できます。
Blume はデフォルトで .md/.mdx ファイルのフォルダーを読み込みます。コンテンツソースを使うと、リモートリポジトリ、CMS、任意のカスタムバックエンドなど、別の場所からページを取得し、複数のソースを1つのサイトに統合できます。ソースはビルド時に読み込まれ、Blume はスタティックファーストのままです。
デフォルトの動作
設定を行わない場合、Blume はコンテンツルート(デフォルトでは docs)を1つの暗黙的なファイルシステムソースとしてスキャンします。トップレベルの content.root/include/exclude オプションはこれまでどおり動作するため、変更は不要です。
import { defineConfig } from "blume";
export default defineConfig({
content: { root: "docs" },
});
複数のソース
ソースを組み合わせるには content.sources 配列を追加します。各エントリは任意の prefix によって名前空間が分けられ、そのルートは /<prefix>/… の下にネストされます。sources が存在する場合は暗黙のデフォルトが置き換えられるため、ローカルドキュメント用に filesystem エントリを含めてください。
import { defineConfig } from "blume";
export default defineConfig({
content: {
sources: [
// Local docs at the site root
{ type: "filesystem", root: "docs" },
// Remote MDX from a GitHub repo, mounted under /sdk
{
type: "mdx-remote",
prefix: "sdk",
github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
},
],
},
});
2つのソースが同じルートに解決される場合、Blume は BLUME_DUPLICATE_ROUTE ビルドエラーを報告します。各ソースには異なる prefix を指定してください。
Obsidian
組み込みの obsidian ソースは、Obsidian の Vault をその場で読み込みます。エクスポート手順は不要で、リポジトリに何かが生成されることもありません。Vault が信頼できる唯一の情報源のままであり、Blume は読み込みの際に Obsidian の方言を Markdown へと変換します。
import { defineConfig } from "blume";
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "docs" },
{
type: "obsidian",
prefix: "notes",
vault: "vault",
// Vault folder names to skip at any depth, on top of dot-folders
exclude: ["Templates", "Daily"],
},
],
},
});
[[Wikilinks]] はルートへのリンクになり、Obsidian がノートを指し示すのと同じように、パスではなく Vault 全体を通じたノート名で解決されます。カスタムのリンクテキスト([[Note|label]])、見出しアンカー([[Note#Install]])、フルパス([[folder/Note]] および [[folder/Note.md]])、Obsidian のデフォルト設定「可能な場合は最短のパス」が書き出す部分パス([[guides/Note]])、そして Obsidian がテーブルセル内で書き出す [[Note\|label]] の形式はいずれも動作し、フロントマターで slug を設定しているノートは、その slug が公開するルートへリンクされます。2つのノートが同じ名前を共有する場合は、Vault のフルパスがちょうどその名前と一致するノートが優先され(Obsidian はリンクを名前より先にパスとして解決します)、次に Vault の順序で最初のもの(Obsidian のファイルエクスプローラーと同じく、大文字小文字を区別せず、ノートよりフォルダーが先)が優先されます。Blume が警告を出すのは、wikilink が実際にそのような衝突を経て解決された場合のみです。曖昧さを解消するには、より長いパスを書いてください。ブロック参照([[Note#^id]])は、アンカーなしでそのノートへリンクします。ブロックは着地先となる id を持たずにレンダリングされるためです。見出しアンカーは対象ノートの実際の見出しに対して解決され、Obsidian のオートコンプリートが書き出すのと同じ方法(**bold**、`code`、リンク記法を取り除いた形)で照合され、ページマニフェストを埋めるのと同じ extractHeadings の処理によって slug 化されます。そのため #Install へのリンクは、どのページも出力しない id ではなく、その見出しに着地します。[[#Install]] は、書いているノート自身の見出しを指します。存在しない見出しへのリンクは、ページへのリンクは維持したままアンカーを削除し、警告を出します。
フロントマターは、Blume のページスキーマが受け付けるものに加え、frontmatter.extend(または、その type のノートについてはコンテンツタイプの frontmatter)で宣言したキーを保持します。それ以外の Obsidian のプロパティ — Dataview のフィールド、Templater の日付、publish、および Obsidian 自身の tags、aliases、cssclasses — は、ノートの変換時に削除されます。そのため、Properties UI で書かれた Vault もフロントマターのエラーなくビルドできます。aliases は解決されるのではなく削除されます。エイリアスによるリンク先はまだサポートされていません。ノートの隣にある相対パスの Markdown 画像()は Vault から提供され、Vault が git リポジトリ内にある場合、Vault のページも他のページと同様に git 由来の「最終更新」日付を取得します。「このページを編集」リンクは github.dir を通じて解決されるため、モノレポでドキュメントアプリの隣に置かれた Vault でもそのファイルへリンクされます。リポジトリ外の Vault にはリンクは付きません。
Vault 内のロケールディレクトリとバージョンスナップショットは、ファイルシステムソースが読み込むのと同じように読み込まれます。i18n を設定していれば fr/Note.md は /fr/ 配下に、バージョンを設定していれば v1.0/Note.md は /v1.0/ 配下に公開され、それらのノートへの wikilink はそれぞれが公開するルートを指します。
index ノートへのリンクは、実体のない /index ではなくそのフォルダーのルートに着地します。解決できない wikilink は、ビルドを失敗させる代わりに、ビルド警告を出したうえでプレーンテキストに縮退します。そのため、リファクタリング途中の Vault でも公開できます。単一行の %%comments%% は取り除かれ、HTML コメント内の wikilink(<!-- [[Draft]] -->)は Obsidian でも非表示になるためそのまま残され、フロントマターに title がないノートはファイル名がタイトルになります — これは Obsidian 自身が適用するのと同じルールです。唯一の例外は index ノートで、これはノートではなくルートを指すため、そのタイトルは Blume の通常の導出(最初の見出し、次に人間可読化したセグメント)にフォールバックします。フェンス付き、インデント付き、およびインラインのコードはそのまま通過するため、この構文を説明するノートも壊れません。
ドットフォルダーはスキップされます。これには Obsidian 自身の .obsidian 設定ディレクトリと .trash が含まれ、開発時のウォッチャーもこれらを無視するため、アプリでペインを移動したりノートをゴミ箱に削除したりしてもサイトは再ビルドされません。ノートの編集では再ビルドされます。どのコンテンツスキャンも読み込まないディレクトリ(node_modules、dist、.git など)もスキップされるため、プロジェクト自体をルートとする Vault が依存関係の README を公開することはありません。Vault 内のシンボリックリンクは、ファイルシステムソースがたどるのと同じようにたどられるため、Vault にリンクされた共有フォルダーも一緒に公開されます。content.root 内にある Vault は、ファイルシステムソースから除外する必要があります(exclude: ["vault/**"])。そうすれば blume version cut はそれをスナップショットから除外し、Vault は自身のノートを引き続き最新版として公開します。
まだ変換されていないもの: コールアウト(> [!note])はプレーンな引用ブロックとしてレンダリングされ、埋め込み(![[image.png]])はそのまま通過し、複数行の %%comments%% はそのまま残され、バックリンクグラフはありません。
リモート MDX
組み込みの mdx-remote ソースは、生の .md/.mdx を HTTP 経由で取得します。ファイルの列挙は、GitHub リポジトリのサブツリー(github)から行うか、raw ベース URL に対して明示的に指定(url + files)します。
{
type: "mdx-remote",
prefix: "sdk",
url: "https://raw.githubusercontent.com/acme/sdk/main/docs",
files: ["intro.mdx", "guide.mdx"],
}
プライベートリポジトリのトークンは GITHUB_TOKEN 環境変数から読み込まれます。設定ファイルや生成された出力にインライン展開されることはなく、送信先は GitHub 自身のホスト(api.github.com、raw.githubusercontent.com)のみで、カスタムの url ベースに送信されることはありません。
リモートページは MDX とコンポーネントの機能をすべて備えた形でレンダリングされます。その本文は隠しステージングディレクトリに実体化され、ローカルドキュメントと並んで Astro を通してレンダリングされるため、コールアウト、タブ、その他すべての Blume コンポーネントが引き続き動作します。
キャッシュとオフラインビルド
各リモートソースは .blume/cache/<source>/ 配下にスナップショットを保持します。ネットワークの一時的な不調や CMS の障害などで取得に失敗した場合、Blume はビルドを失敗させる代わりに、警告を出しつつ最後に取得できた正常なスナップショットを提供します。キャッシュは .blume/ 内に置かれ、再生成されるものであり、コミットすることはありません。
開発環境では、リモートコンテンツは一度だけ取得され、そのセッション中は固定されます。更新するには開発サーバーを再起動してください。ローカルのファイルシステムソースは通常どおりホットリロードされます。代わりにリモートソースの変更をポーリングするには、そのソースに pollInterval(秒)を設定します。開発サーバーはその間隔で再取得を行い、コンテンツが実際に変更された場合にのみリロードします。作業中に API へアクセスしないようにするには、未設定のままにしてください。
GitHub Releases
組み込みの github-releases ソースは、リポジトリのリリースを変更履歴(changelog)に変換します。各リリースは type: changelog のエントリになるため、リリースノートがそのまま変更履歴になり、二重に書く必要はありません。生成される変更履歴タイムラインと組み合わせれば、GitHub リリースを公開するだけで変更履歴エントリが出荷されます。
import { defineConfig } from "blume";
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "content" },
{
type: "github-releases",
prefix: "changelog",
owner: "acme",
repo: "sdk",
// prereleases: false, // include prereleases (default off)
// drafts: false, // include drafts (needs a write token)
// limit: 100, // cap releases, newest-first
},
],
},
});
各リリースは変更履歴のフィールドへ自動的にマッピングされます。名前(またはタグ)がタイトルになり、公開日がタイムラインの並び順を決定し、タグが changelog.version になり、プレリリースには Prerelease(それ以外には Release)のタグが付きます。リリースノートはエントリの本文としてレンダリングされます。リリースページを /changelog/v1-2-0 のようなルートにネストさせるには、ソースに prefix を指定してください。
プライベートリポジトリの認証には GITHUB_TOKEN 環境変数を使用します。これは他の GitHub 機能が使うものと同じトークンで、設定ファイルにインライン展開されることはありません。他のすべてのリモートソースと同様に .blume/cache/<source>/ にキャッシュされ、API に到達できない場合はオフラインで提供されます。変更履歴は補助的なものであるため、キャッシュがない状態で取得に失敗した場合(トークンのない CI ビルドなど)は、ビルドを失敗させるのではなく、警告を出して空の変更履歴に縮退します。内容を反映させるには、CI やデプロイ環境で GITHUB_TOKEN を設定してください。
Sanity
組み込みの sanity ソースは GROQ クエリを実行し、各ドキュメントのフィールドをフロントマターに、Portable Text の本文を Markdown にマッピングします。@sanity/client パッケージは任意の peer dependency です。このソースを使う場合にのみインストールしてください。
import { defineConfig } from "blume";
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "docs" },
{
type: "sanity",
prefix: "guides",
projectId: "abc123",
dataset: "production",
query: `*[_type == "guide"]`,
// Field paths default to title / slug.current / body / _updatedAt
fields: { slug: "slug.current", body: "content" },
},
],
},
});
プライベートデータセット用の読み取りトークンは SANITY_TOKEN 環境変数から取得されます。カスタムの Portable Text ブロックタイプは、アダプターの serializers オプションを通じて Blume コンポーネントにマッピングできます。このオプションはカスタムソースとして sanitySource を直接構築する場合に利用できます。
Notion
組み込みの notion ソースは、Notion のデータベースをコレクションに変換します。各行がページになり、そのプロパティがフロントマターに、ブロックツリーが MDX になります。コールアウト、トグル、カラム、コードブロックは対応する Blume コンポーネントにマッピングされます。動画ブロックは、YouTube のリンクを保持している場合は <YouTube> 埋め込みに、それ以外の場合は <video> プレーヤーになり、いずれの場合もブロックのキャプションが <Frame> のキャプションになります。メディアファイルではなく動画ページへのリンク(たとえば Vimeo や Loom の URL)は、埋め込まれる代わりに警告として報告されます。@notionhq/client(v5 以降)は任意の peer dependency です。Blume はデータベースをその最初のデータソースを通じて読み込みます。
import { defineConfig } from "blume";
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "docs" },
{
type: "notion",
prefix: "handbook",
database: process.env.NOTION_DB_ID,
// Property names default to the title-typed prop / Description / Slug / Order
// Set publishedValue to treat Status as a publish gate (opt-in)
publishedValue: "Published",
},
],
},
});
インテグレーショントークンは NOTION_TOKEN 環境変数から取得されます(データベースをインテグレーションと共有してください)。デフォルトではすべてのページがインポートされます。Status プロパティを公開ゲートとして扱うには publishedValue を設定してください。その場合、それ以外の値は draft: true にマッピングされ、本番ビルドでは除外されます。Notion の画像および動画の URL は署名付きで有効期限があるため、アダプターはビルド時にそれらをダウンロードしてサイトのアセットに取り込み、参照を書き換えます。CMS のアセットが静的ビルドを壊すことはありません。API 呼び出しは小さなリクエストプール(Notion のインテグレーションごとのレート制限に合わせて同時3件)を通してペース調整されるため、数百ページ規模のデータベースでも 429 レスポンスを引き起こすことなくインポートできます。調整するには、ソースに concurrency を設定してください。
プレビューと同期
リモートコンテンツの取得方法と対象範囲は、2つのフラグで制御します。
--previewをblume devまたはblume buildに付けると、下書きがレンダリングされ、未公開の CMS コンテンツが取得されます。Sanity はpreviewDraftsパースペクティブに切り替わり、Notion はStatusによるフィルタリングを停止します。フラグなしの本番ビルドはこれまでどおり下書きを除外するため、プレビュービルドは出荷前に未公開の作業を安全に確認する手段になります。blume syncはすべてのリモートソースを再取得し、ランタイムを再生成します。開発環境はキャッシュ優先で、リモートソースは一度取得されると再起動時には.blume/cacheから提供されます(高速でオフラインにも強い)。そのため、開発サーバーを再起動せずに最新の CMS コンテンツを取得する手段がblume syncです(起動中のサーバーはホットリロードします)。先にキャッシュを破棄するには--forceを追加するか、ソースにpollIntervalを設定して自動更新してください。
blume dev --preview # author workflow: see drafts live
blume build --preview # render a full preview build
blume sync # refresh remote content now
blume sync --force # ...ignoring any cached snapshot
カスタムソース
ContentSource インターフェースを実装したオブジェクトはそのまま渡せます。これにより、カスタムシリアライザーを備えたアダプターや、組み込みでない任意のバックエンドを、その SDK をコアのインストールに持ち込むことなく組み込めます。
import { defineConfig } from "blume";
import { sanitySource } from "blume/sources/sanity.ts";
export default defineConfig({
content: {
sources: [
{ type: "filesystem", root: "docs" },
{
type: "custom",
source: sanitySource({
name: "guides",
prefix: "guides",
projectId: "abc123",
dataset: "production",
query: `*[_type == "guide"]`,
// Map custom Portable Text blocks to Blume components
serializers: {
callout: (block) => `<Callout>${block.text}</Callout>`,
},
}),
},
],
},
});
ソースは自身のネイティブな形式(Portable Text、Notion ブロック、リモート HTML)を Markdown/MDX テキストに正規化するため、ページがどこから来たものであっても、同じコンポーネントと Markdown の機能が適用されます。
ローカルファイルを読み込むカスタムソースでは、各エントリに sourcePath を、ソース自体に contentRoot を設定してください。sourcePath は診断メッセージ内でファイルを示し、その隣にある相対パスの画像を解決します。contentRoot はページの日付を求める git の log の範囲を限定するもので、これがないとそのソースのページは git 由来の「最終更新」日付を取得できません。