基本の契約
どの拡張ポイントを使うか
プラグインを作るときは、まず目的に合った最小の拡張ポイントを選びます。
Diagram source
flowchart TD
Q{"何を追加したい?"}
Q -->|"MarkdownやHTMLの意味変換"| Pipeline["remark / rehype<br/>パイプライン"]
Q -->|"コンテンツ処理の特定段階へ参加"| Hook["コンテンツフック"]
Q -->|"特定の形式を表示"| Renderer["renderers"]
Q -->|"独立したページ"| Page["pageTypes"]
Q -->|"CSS"| Asset["assets"]
Q -->|"ブラウザ上の処理"| Client["clientEntries"]
Q -->|"HTTP API"| Endpoint["endpoints"]
Q -->|"SEO"| SEO["seo"]
Q -->|"グラフ情報"| Graph["extendContentGraph"]
Q -->|"問題の報告"| Diagnostics["addDiagnostics"]
たとえば、Canvas や Excalidraw を記事内へ表示するなら renderers、/explore のような独立したページを提供するなら pageTypes を使います。独立した画面が必要だからといって、プラグイン固有の HonoX ルートを追加するわけではありません。
最小のプラグイン
最も小さいプラグインは、次のように作れます。
import { definePlugin } from "@riebeckite/core";
export function example() {
return definePlugin({
name: "example",
});
}
プラグインに設定を持たせる場合は、ファクトリ関数の引数として受け取ります。
type ExampleOptions = {
enabled?: boolean;
};
export function example(
options: ExampleOptions = {},
) {
return definePlugin({
name: "example",
options,
});
}
definePlugin() がプラグイン共通の取り決めを提供します。
プラグインが持てる機能
RiebeckitePlugin には、主に次の拡張ポイントがあります。
| 分類 | 主な API |
|---|---|
| 基本情報 | name, enabled, order, options |
| 依存関係 | provides, requires, optional |
| 設定の検証 | validateOptions |
| キャッシュ | cacheVersion, context.cache |
| ライフサイクル | setup, buildStart, buildEnd, dispose |
| コンテンツ | onConfigResolved, onContentLoaded, onPostParsed, onPostProcessed, onManifestCreated |
| 公開先 | resolveContentLocations |
| Markdown / HTML | remarkPlugins, rehypePlugins, extendMarkdownPipeline, extendHtmlPipeline |
| グラフ | extendContentGraph |
| 診断 | addDiagnostics |
| API ライフサイクル | apiLifecycle |
| 埋め込み表示 | renderers |
| 独立ページ | pageTypes |
| ブラウザ | assets, clientEntries |
| HTTP | endpoints |
| SEO | seo |
| インスペクターのタブ | inspector |
この中から、必要なものだけを使ってください。
非推奨のプラグインオプションやフックは apiLifecycle で宣言すると、check、doctor、インスペクターから報告できます。詳しくは API ライフサイクル を参照してください。
開発時のルートマニフェスト
開発時は、記事 HTML を処理する前に frontmatter とリンクのメタデータでルートを解決します。extendContentGraph または onManifestCreated を使うプラグインでは、entry.html === "" でも正しく処理でき、生成物やアセット変換を必要とせず、開発サーバーでも実行する必要がある場合に限り、developmentRouteManifest: true を指定します。それ以外のマニフェストへ影響するプラグインは、互換性を保つため開発時も Full Manifest を使います。
プラグインを有効にする
プラグインは、riebeckite.config.ts の plugins へ追加します。
plugins: [
my(),
]
条件付きで有効化することもできます。
plugins: [
condition && my(),
]
false、null、undefined は、プラグインを解決するときに除外されます。また、enabled: false を指定したプラグインも実行されません。
enabled: false
実行順と依存関係
order は、変換やレンダラーの優先順を一定にするための基本順です。プラグイン同士に機能の提供・利用関係がなく、パイプライン内の実行順だけを決めたい場合に使います。
一方、プラグイン同士に実際の提供・利用関係がある場合は、order ではなく**ケイパビリティ**を使います。変換順を表すためだけのケイパビリティは作成しないでください。
definePlugin({
name: "consumer",
provides: [
"example.output",
],
requires: [
"content.graph",
],
optional: [
"example.optional",
],
});
それぞれの意味は次のとおりです。
| 項目 | 意味 |
|---|---|
provides |
このプラグインが提供する機能 |
requires |
必ず必要な機能 |
optional |
あれば利用する機能 |
依存関係の解決処理は、提供側が利用側より前に来るよう実行順を決めます。
Diagram source
flowchart LR
Provider["Provider Plugin<br/>provides: content.graph"]
Consumer["Consumer Plugin<br/>requires: content.graph"]
Provider --> Consumer
次の状態は設定エラーになります。
- 必須のケイパビリティが存在しない
- 同じケイパビリティを複数のプラグインが提供している
- 依存関係が循環している
解決に失敗した場合は、PluginDependencyError(@riebeckite/core からインポート可能)を投げます。kind と pluginName は原因を特定できますが、duplicate-provider や cycle の場合は pluginName が設定されておらず、pluginNames / chain を使って原因を特定してください。依存関係のないプラグインについては、入力順をできるだけ維持します。
オプションの検証
TypeScript の型だけでは、実行時に渡される設定値が必ず正しいとは限りません。必要なプラグインは validateOptions を実装できます。
検証は、次の流れで行われます。
プラグインのオプション
↓
validateOptions
↓
構造化された問題
↓
設定の検証
検証処理では、設定の確認だけを行ってください。ファイルシステムの走査、ビルド、キャッシュへの書き込み、外部状態の変更などは行いません。問題は構造化された値として返し、Riebeckite の設定検証がまとめて表示できるようにします。
プラグインに渡されるコンテキスト
プラグインは、フレームワークの機能を PluginContext から受け取ります。基本的なコンテキストは、概念的には次のようなものです。
type PluginContext = {
config?: ResolvedRiebeckiteConfig;
isDevelopment?: boolean;
/**
* Deterministic build clock. Publishing, scheduling, and time-dependent
* plugin output share this value so one build always agrees with itself.
*/
buildTime?: Date;
contentIndex: Map<string, string>;
diagnostics: Diagnostic[];
cache: PluginCache;
derived?: DerivedViewRegistry;
/**
* Registers a file to be written to the build output directory. Available
* during build lifecycle hooks; the integration layer forwards each entry to
* the build tool.
*/
externalDependencies?: ReadonlyMap<string, ExternalDependencySnapshot>;
output: GeneratedOutputSink;
logger: Logger;
tracer: Tracer;
contentSource?: ContentSource;
};
isDevelopment は Vite の開発ランタイムでのみ true です。開発サーバーが配信しない静的出力をプラグインが遅延させるために使えます。
フックによって、slug、markdown、content、manifest、entries、公開先を決めるための入力などが追加されます。
プラグイン内でグローバルなシングルトンを作るより、コンテキストからフレームワークの機能を受け取ってください。
ライフサイクル
プラグインには、フレームワーク全体のライフサイクルと、コンテンツ処理のライフサイクルがあります。
フレームワーク側の主なフックは setup、buildStart、buildEnd です。dispose は、プラグインが確保したリソースを解放するために使います。
setup、buildStart、onConfigResolved、コンテンツ処理、buildEnd は、1つの ContentManager につき一度だけ実行されます。buildEnd は、診断情報を集めたあとに完成したマニフェストを受け取る唯一の終了フックです。dispose は、解決済みプラグインの逆順で実行されます。
名前付きのライフサイクルフックやコンテンツフックでエラーが起きた場合は、そのまま上位へ伝えます。エラーは PluginHookError(@riebeckite/core からインポート可能)です。message は Plugin "<name>" failed during "<hook>" の形式になり、元のエラーは cause に保持されます。コンテンツフックで起きた場合は、対象ファイルが path(例: note.md)として設定されます。