Color mode

Architecture

Riebeckite は、Core を中心に Plugin、Integration、Theme、Application を分離した pnpm workspace です。

重要な原則は、内側の package が外側の実装を知らないことです。

たとえば Core はコンテンツ処理の仕組みを提供しますが、

  • HonoX でどう表示するか
  • Vite でどう build するか
  • どの Plugin がインストールされているか
  • Site がどんな UI を持つか

といったことは知りません。

Diagram source
text
flowchart BT
    App["Application<br/>apps/web"]
    Integration["Integration<br/>packages/integrations/*"]
    CLI["CLI<br/>packages/cli"]
    Plugin["Plugin<br/>packages/plugins/*"]
    Theme["Theme<br/>packages/themes/*"]
    Core["Core<br/>packages/core"]
 
    App --> Integration
    Integration --> Core
    CLI --> Integration
    CLI --> Core
    Plugin --> Core
    Theme --> Core

矢印は依存方向です。

Core から Plugin、Integration、Application などへの逆向きの依存は作りません。

Package の責務

Riebeckite のコードは、責務ごとに package を分けています。

場所 主な責務
packages/core Riebeckite の共通基盤
packages/plugins/* 再利用可能な機能拡張
packages/integrations/* Framework / Bundler / Platform との接続
packages/themes/* Theme と CSS
packages/cli CLI と Node 上の Build Tooling
apps/web 実際の Site Application

Core

text
packages/core

Core は、特定の Web framework に依存しない Riebeckite の基盤です。

主に次の機能を所有します。

  • config
  • content orchestration
  • manifest
  • content graph
  • pipeline
  • Plugin runtime
  • observability
  • Theme contract
  • 共通の型や lifecycle contract

Core は portable であることを重視します。

そのため、

text
HonoX
Vite
Cloudflare
特定の Plugin
Site 固有の UI

などへ依存させません。

Plugin

text
packages/plugins/*

Plugin は、複数の Site で再利用できる機能を追加します。

たとえば、

  • Markdown の変換
  • HTML の変換
  • metadata の追加
  • asset の生成
  • browser-side behavior
  • Page Type の提供

などです。

Plugin は Core が公開している contract を利用して機能を拡張します。

Diagram source
text
flowchart LR
    Plugin["Plugin"] --> Contract["Core Plugin Contract"]
    Contract --> Pipeline["Content Pipeline"]

Plugin のために Core が特定 Plugin の実装を知るような依存関係にはしません。

Integration

text
packages/integrations/*

Integration は Riebeckite と外部技術を接続します。

たとえば @riebeckite/honox は、

text
Riebeckite Core
      ↕
HonoX / Vite

を接続する役割を持ちます。

Framework、Bundler、Platform 固有の処理は Core ではなく Integration に配置します。

詳しくは HonoX Integration を参照してください。

Theme

text
packages/themes/*

Theme は Site の見た目を変更します。

主に、

  • CSS
  • semantic token
  • stable CSS hook
  • data-* attribute
  • CSS cascade

を利用します。

Theme は presentation を担当しますが、Site の構造そのものは所有しません。

そのため Theme が、

  • route
  • Page Type ID
  • application component
  • Site の page composition

を所有することはありません。

Site Application

text
apps/web

apps/web は実際の Riebeckite Site Application です。

主に、

  • routes
  • application components
  • islands
  • page composition
  • Site shell
  • Workers との接続

を所有します。

Core や Integration が「どう表示するか」まで決めるのではなく、最終的な Site の構造は Application が決定します。

CLI

text
packages/cli

CLI は Node.js 上で実行される command と build tooling を担当します。

たとえば、

sh
riebeckite build
riebeckite check
riebeckite doctor
riebeckite inspect
riebeckite profile

などの入口を提供します。

CLI は必要に応じて Core や Integration の機能を呼び出します。

Content の流れ

コンテンツ処理では、大きく ContentSource と ContentManager の責務を分離しています。

Diagram source
text
flowchart LR
    Source["ContentSource"]
    Manager["ContentManager"]
    Location["Public Location"]
    Pipeline["Parse / Pipeline<br/>Plugin Hooks"]
    Result["Manifest / Graph"]
    App["Application"]
 
    Source -->|"scan / read / metadata"| Manager
    Manager --> Location
    Location --> Pipeline
    Pipeline --> Result
    Result --> App
 
    Plugins["Plugins"] -->|"hooks"| Pipeline

ContentSource

ContentSource は「コンテンツをどこから、どう読み込むか」を担当します。

主に、

  • scan
  • read
  • content identity
  • mtime
  • size
  • ETag
  • hash

など、source に関する情報を提供します。

たとえば filesystem を使う Content Source ならファイルを読み込みますが、ContentManager 自身が filesystem を直接探索するわけではありません。

新しい Content Source を追加するときも、この contract を通します。

ContentManager

ContentManager は読み込まれたコンテンツを処理します。

主に、

  • public location の解決
  • parse
  • pipeline
  • Plugin hooks
  • manifest
  • content graph

を担当します。

つまり、

text
ContentSource
    ↓
コンテンツを取得する
 
ContentManager
    ↓
コンテンツを解決・処理する

という分担です。

ContentManager に独自の filesystem scan を追加するのではなく、ContentSource の contract を利用してください。

Public URL の解決

コンテンツの URL は filesystem path や slug から推測しません。

Riebeckite が明示的に Public Location を解決します。

基本的な流れは次のようになります。

Diagram source
text
flowchart LR
    Content["Content"]
    Default["resolveDefaultContentLocation()"]
    Hooks["resolveContentLocations<br/>Plugin Hooks"]
    Manager["ContentManager<br/>getContentLocations()"]
    Permalink["Resolved permalink"]
    Consumer["Consumer"]
 
    Content --> Default
    Default --> Hooks
    Hooks --> Manager
    Manager --> Permalink
    Permalink --> Consumer

Consumer は、この処理によって確定した permalink を使用します。

たとえば、

text
content/posts/hello.md

という filesystem path があっても、

text
/posts/hello

になるとは限りません。

Plugin や config によって、

text
/blog/hello/

へ解決されているなら、それが正式な公開 URL です。

そのため Consumer 側で、

text
filesystem path → slug → URL

のような再計算をしないでください。

解決済みの permalink が public URL の source of truth です。

Page Type

Plugin は Page Type を使って、通常の Markdown content とは異なるページを提供できます。

ただし Page Type は特定の Web framework に依存しません。

Diagram source
text
flowchart LR
    Plugin["Plugin"]
    Page["Page Type<br/>path + body"]
    Integration["Integration<br/>Route Resolution"]
    Frame["Application<br/>Document Frame"]
 
    Plugin --> Page
    Page --> Integration
    Integration --> Frame

Plugin が提供するのは、主に public path と page body です。

それを実際の URL として処理するのは Integration、最終的な HTML document として組み立てるのは Application です。

Plugin が HonoX route や Site shell を所有する必要はありません。

詳しくは Page System を参照してください。

Build-time と Runtime

Riebeckite では、Build 時にだけ必要なものと、公開 Site の Runtime で必要なものを分離します。

Diagram source
text
flowchart LR
    subgraph Build["Build-time / Node.js"]
        CLI["CLI"]
        Doctor["Doctor"]
        Inspector["Inspector"]
        State["Incremental State"]
        Cache["Plugin Cache"]
        Profile["Profile / Trace"]
    end
 
    Build --> Output["Generated Application<br/>Stable Content Data"]
 
    Output --> Runtime["Runtime<br/>Cloudflare Workers"]

次のものは Build-time の情報です。

  • .riebeckite/build/content-state.json
  • Plugin Cache
  • CLI
  • Doctor
  • Inspector
  • Profile / Trace

Cloudflare Workers の request runtime は、これらを読み書きしません。

Runtime が利用するのは Build によって生成された application と、安定した content data です。

これにより、Build の最適化用 state と公開 Site の動作を分離しています。

また Build が失敗した場合、新しい不完全な state で以前の正常な state を置き換えません。

コードをどこに置くか

新しい機能を追加するときは、まず「どの package がその責務を持つべきか」を考えます。

Diagram source
text
flowchart TD
    Q{"何を追加する?"}
 
    Q -->|"共通の型・contract・lifecycle"| Core["Core"]
    Q -->|"再利用可能なContent機能"| Plugin["Plugin"]
    Q -->|"HonoX / Vite / Platform接続"| Integration["Integration"]
    Q -->|"Route / Page / Island"| App["Site Application"]
    Q -->|"見た目・CSS・Token"| Theme["Theme"]

判断の目安は次のとおりです。

追加するもの 配置先
持ち運べる型や lifecycle contract Core
再利用可能な content の振る舞い Plugin
Vite / HonoX / Platform との接続 Integration
Route / Page composition / Island Site Application
Visual token / CSS Theme

迷った場合は、その機能を成立させるために必要な最も内側の package に置きます。

ただし、内側の package から外側へ依存させてはいけません。

たとえば、

text
「Plugin Page を HonoX で表示したい」

からといって Core に HonoX のコードを追加するのではなく、

text
Core
  → framework-independent な Page contract
 
Plugin
  → Page を提供
 
HonoX Integration
  → Page を route と接続
 
Site
  → 最終的な document を描画

と責務を分割します。

この境界を維持することで、Core や Plugin を特定の Site、Framework、Platform に固定せず再利用できます。

関連する設計については Content System、Plugin System、Theme System、HonoX Integration を参照してください。

History

1 changesCollapseExpand
1 + # Architecture
2 +
3 + Riebeckite は、Core を中心に Plugin、Integration、Theme、Application を分離した pnpm workspace です。
4 +
5 + 重要な原則は、**内側の package が外側の実装を知らないこと**です。
6 +
7 + たとえば Core はコンテンツ処理の仕組みを提供しますが、
8 +
9 + - HonoX でどう表示するか
10 + - Vite でどう build するか
11 + - どの Plugin がインストールされているか
12 + - Site がどんな UI を持つか
13 +
14 + といったことは知りません。
15 +
16 + ```mermaid id="p4fcge"
17 + flowchart BT
18 + App["Application<br/>apps/web"]
19 + Integration["Integration<br/>packages/integrations/*"]
20 + CLI["CLI<br/>packages/cli"]
21 + Plugin["Plugin<br/>packages/plugins/*"]
22 + Theme["Theme<br/>packages/themes/*"]
23 + Core["Core<br/>packages/core"]
24 +
25 + App --> Integration
26 + Integration --> Core
27 + CLI --> Integration
28 + CLI --> Core
29 + Plugin --> Core
30 + Theme --> Core
31 + ```
32 +
33 + 矢印は依存方向です。
34 +
35 + Core から Plugin、Integration、Application などへの逆向きの依存は作りません。
36 +
37 + # Package の責務
38 +
39 + Riebeckite のコードは、責務ごとに package を分けています。
40 +
41 + | 場所 | 主な責務 |
42 + | --- | --- |
43 + | `packages/core` | Riebeckite の共通基盤 |
44 + | `packages/plugins/*` | 再利用可能な機能拡張 |
45 + | `packages/integrations/*` | Framework / Bundler / Platform との接続 |
46 + | `packages/themes/*` | Theme と CSS |
47 + | `packages/cli` | CLI と Node 上の Build Tooling |
48 + | `apps/web` | 実際の Site Application |
49 +
50 + ## Core
51 +
52 + ```text id="cxz98n"
53 + packages/core
54 + ```
55 +
56 + Core は、特定の Web framework に依存しない Riebeckite の基盤です。
57 +
58 + 主に次の機能を所有します。
59 +
60 + - config
61 + - content orchestration
62 + - manifest
63 + - content graph
64 + - pipeline
65 + - Plugin runtime
66 + - observability
67 + - Theme contract
68 + - 共通の型や lifecycle contract
69 +
70 + Core は portable であることを重視します。
71 +
72 + そのため、
73 +
74 + ```text id="zpfh9w"
75 + HonoX
76 + Vite
77 + Cloudflare
78 + 特定の Plugin
79 + Site 固有の UI
80 + ```
81 +
82 + などへ依存させません。
83 +
84 + ## Plugin
85 +
86 + ```text id="56z84x"
87 + packages/plugins/*
88 + ```
89 +
90 + Plugin は、複数の Site で再利用できる機能を追加します。
91 +
92 + たとえば、
93 +
94 + - Markdown の変換
95 + - HTML の変換
96 + - metadata の追加
97 + - asset の生成
98 + - browser-side behavior
99 + - Page Type の提供
100 +
101 + などです。
102 +
103 + Plugin は Core が公開している contract を利用して機能を拡張します。
104 +
105 + ```mermaid id="79dvqt"
106 + flowchart LR
107 + Plugin["Plugin"] --> Contract["Core Plugin Contract"]
108 + Contract --> Pipeline["Content Pipeline"]
109 + ```
110 +
111 + Plugin のために Core が特定 Plugin の実装を知るような依存関係にはしません。
112 +
113 + ## Integration
114 +
115 + ```text id="n94ykv"
116 + packages/integrations/*
117 + ```
118 +
119 + Integration は Riebeckite と外部技術を接続します。
120 +
121 + たとえば `@riebeckite/honox` は、
122 +
123 + ```text id="ldd2oa"
124 + Riebeckite Core
125 + ↕
126 + HonoX / Vite
127 + ```
128 +
129 + を接続する役割を持ちます。
130 +
131 + Framework、Bundler、Platform 固有の処理は Core ではなく Integration に配置します。
132 +
133 + 詳しくは [HonoX Integration](honox-integration.md) を参照してください。
134 +
135 + ## Theme
136 +
137 + ```text id="1as7cf"
138 + packages/themes/*
139 + ```
140 +
141 + Theme は Site の見た目を変更します。
142 +
143 + 主に、
144 +
145 + - CSS
146 + - semantic token
147 + - stable CSS hook
148 + - `data-*` attribute
149 + - CSS cascade
150 +
151 + を利用します。
152 +
153 + Theme は presentation を担当しますが、Site の構造そのものは所有しません。
154 +
155 + そのため Theme が、
156 +
157 + - route
158 + - Page Type ID
159 + - application component
160 + - Site の page composition
161 +
162 + を所有することはありません。
163 +
164 + ## Site Application
165 +
166 + ```text id="9otuxo"
167 + apps/web
168 + ```
169 +
170 + `apps/web` は実際の Riebeckite Site Application です。
171 +
172 + 主に、
173 +
174 + - routes
175 + - application components
176 + - islands
177 + - page composition
178 + - Site shell
179 + - Workers との接続
180 +
181 + を所有します。
182 +
183 + Core や Integration が「どう表示するか」まで決めるのではなく、最終的な Site の構造は Application が決定します。
184 +
185 + ## CLI
186 +
187 + ```text id="k0pqbm"
188 + packages/cli
189 + ```
190 +
191 + CLI は Node.js 上で実行される command と build tooling を担当します。
192 +
193 + たとえば、
194 +
195 + ```sh id="kt0j27"
196 + riebeckite build
197 + riebeckite check
198 + riebeckite doctor
199 + riebeckite inspect
200 + riebeckite profile
201 + ```
202 +
203 + などの入口を提供します。
204 +
205 + CLI は必要に応じて Core や Integration の機能を呼び出します。
206 +
207 + # Content の流れ
208 +
209 + コンテンツ処理では、大きく `ContentSource` と `ContentManager` の責務を分離しています。
210 +
211 + ```mermaid id="8k9ikv"
212 + flowchart LR
213 + Source["ContentSource"]
214 + Manager["ContentManager"]
215 + Location["Public Location"]
216 + Pipeline["Parse / Pipeline<br/>Plugin Hooks"]
217 + Result["Manifest / Graph"]
218 + App["Application"]
219 +
220 + Source -->|"scan / read / metadata"| Manager
221 + Manager --> Location
222 + Location --> Pipeline
223 + Pipeline --> Result
224 + Result --> App
225 +
226 + Plugins["Plugins"] -->|"hooks"| Pipeline
227 + ```
228 +
229 + ## ContentSource
230 +
231 + `ContentSource` は「コンテンツをどこから、どう読み込むか」を担当します。
232 +
233 + 主に、
234 +
235 + - scan
236 + - read
237 + - content identity
238 + - `mtime`
239 + - size
240 + - ETag
241 + - hash
242 +
243 + など、source に関する情報を提供します。
244 +
245 + たとえば filesystem を使う Content Source ならファイルを読み込みますが、`ContentManager` 自身が filesystem を直接探索するわけではありません。
246 +
247 + 新しい Content Source を追加するときも、この contract を通します。
248 +
249 + ## ContentManager
250 +
251 + `ContentManager` は読み込まれたコンテンツを処理します。
252 +
253 + 主に、
254 +
255 + - public location の解決
256 + - parse
257 + - pipeline
258 + - Plugin hooks
259 + - manifest
260 + - content graph
261 +
262 + を担当します。
263 +
264 + つまり、
265 +
266 + ```text id="g1g93m"
267 + ContentSource
268 + ↓
269 + コンテンツを取得する
270 +
271 + ContentManager
272 + ↓
273 + コンテンツを解決・処理する
274 + ```
275 +
276 + という分担です。
277 +
278 + ContentManager に独自の filesystem scan を追加するのではなく、`ContentSource` の contract を利用してください。
279 +
280 + # Public URL の解決
281 +
282 + コンテンツの URL は filesystem path や slug から推測しません。
283 +
284 + Riebeckite が明示的に **Public Location** を解決します。
285 +
286 + 基本的な流れは次のようになります。
287 +
288 + ```mermaid id="i6wxn4"
289 + flowchart LR
290 + Content["Content"]
291 + Default["resolveDefaultContentLocation()"]
292 + Hooks["resolveContentLocations<br/>Plugin Hooks"]
293 + Manager["ContentManager<br/>getContentLocations()"]
294 + Permalink["Resolved permalink"]
295 + Consumer["Consumer"]
296 +
297 + Content --> Default
298 + Default --> Hooks
299 + Hooks --> Manager
300 + Manager --> Permalink
301 + Permalink --> Consumer
302 + ```
303 +
304 + Consumer は、この処理によって確定した `permalink` を使用します。
305 +
306 + たとえば、
307 +
308 + ```text id="ez3dhv"
309 + content/posts/hello.md
310 + ```
311 +
312 + という filesystem path があっても、
313 +
314 + ```text id="qysr09"
315 + /posts/hello
316 + ```
317 +
318 + になるとは限りません。
319 +
320 + Plugin や config によって、
321 +
322 + ```text id="i4omv8"
323 + /blog/hello/
324 + ```
325 +
326 + へ解決されているなら、それが正式な公開 URL です。
327 +
328 + そのため Consumer 側で、
329 +
330 + ```text id="crklqd"
331 + filesystem path → slug → URL
332 + ```
333 +
334 + のような再計算をしないでください。
335 +
336 + **解決済みの `permalink` が public URL の source of truth です。**
337 +
338 + # Page Type
339 +
340 + Plugin は Page Type を使って、通常の Markdown content とは異なるページを提供できます。
341 +
342 + ただし Page Type は特定の Web framework に依存しません。
343 +
344 + ```mermaid id="cs3lxy"
345 + flowchart LR
346 + Plugin["Plugin"]
347 + Page["Page Type<br/>path + body"]
348 + Integration["Integration<br/>Route Resolution"]
349 + Frame["Application<br/>Document Frame"]
350 +
351 + Plugin --> Page
352 + Page --> Integration
353 + Integration --> Frame
354 + ```
355 +
356 + Plugin が提供するのは、主に public path と page body です。
357 +
358 + それを実際の URL として処理するのは Integration、最終的な HTML document として組み立てるのは Application です。
359 +
360 + Plugin が HonoX route や Site shell を所有する必要はありません。
361 +
362 + 詳しくは [Page System](./page-system.md) を参照してください。
363 +
364 + # Build-time と Runtime
365 +
366 + Riebeckite では、Build 時にだけ必要なものと、公開 Site の Runtime で必要なものを分離します。
367 +
368 + ```mermaid id="qdk05v"
369 + flowchart LR
370 + subgraph Build["Build-time / Node.js"]
371 + CLI["CLI"]
372 + Doctor["Doctor"]
373 + Inspector["Inspector"]
374 + State["Incremental State"]
375 + Cache["Plugin Cache"]
376 + Profile["Profile / Trace"]
377 + end
378 +
379 + Build --> Output["Generated Application<br/>Stable Content Data"]
380 +
381 + Output --> Runtime["Runtime<br/>Cloudflare Workers"]
382 + ```
383 +
384 + 次のものは Build-time の情報です。
385 +
386 + - `.riebeckite/build/content-state.json`
387 + - Plugin Cache
388 + - CLI
389 + - Doctor
390 + - Inspector
391 + - Profile / Trace
392 +
393 + Cloudflare Workers の request runtime は、これらを読み書きしません。
394 +
395 + Runtime が利用するのは Build によって生成された application と、安定した content data です。
396 +
397 + これにより、Build の最適化用 state と公開 Site の動作を分離しています。
398 +
399 + また Build が失敗した場合、新しい不完全な state で以前の正常な state を置き換えません。
400 +
401 + # コードをどこに置くか
402 +
403 + 新しい機能を追加するときは、まず「どの package がその責務を持つべきか」を考えます。
404 +
405 + ```mermaid id="hdzg9c"
406 + flowchart TD
407 + Q{"何を追加する?"}
408 +
409 + Q -->|"共通の型・contract・lifecycle"| Core["Core"]
410 + Q -->|"再利用可能なContent機能"| Plugin["Plugin"]
411 + Q -->|"HonoX / Vite / Platform接続"| Integration["Integration"]
412 + Q -->|"Route / Page / Island"| App["Site Application"]
413 + Q -->|"見た目・CSS・Token"| Theme["Theme"]
414 + ```
415 +
416 + 判断の目安は次のとおりです。
417 +
418 + | 追加するもの | 配置先 |
419 + | --- | --- |
420 + | 持ち運べる型や lifecycle contract | **Core** |
421 + | 再利用可能な content の振る舞い | **Plugin** |
422 + | Vite / HonoX / Platform との接続 | **Integration** |
423 + | Route / Page composition / Island | **Site Application** |
424 + | Visual token / CSS | **Theme** |
425 +
426 + 迷った場合は、**その機能を成立させるために必要な最も内側の package** に置きます。
427 +
428 + ただし、内側の package から外側へ依存させてはいけません。
429 +
430 + たとえば、
431 +
432 + ```text id="mfpzfj"
433 + 「Plugin Page を HonoX で表示したい」
434 + ```
435 +
436 + からといって Core に HonoX のコードを追加するのではなく、
437 +
438 + ```text id="qlfw3c"
439 + Core
440 + → framework-independent な Page contract
441 +
442 + Plugin
443 + → Page を提供
444 +
445 + HonoX Integration
446 + → Page を route と接続
447 +
448 + Site
449 + → 最終的な document を描画
450 + ```
451 +
452 + と責務を分割します。
453 +
454 + この境界を維持することで、Core や Plugin を特定の Site、Framework、Platform に固定せず再利用できます。
455 +
456 + 関連する設計については [Content System](content-system.md)、[Plugin System](plugin-system.md)、[Theme System](theme-system.md)、[HonoX Integration](honox-integration.md) を参照してください。
457 +