@tenjuu99/blog 0.2.56 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -79
- package/docs/develop.md +521 -0
- package/docs/spec.md +849 -0
- package/docs/tutorial.md +165 -0
- package/package.json +4 -2
- package/packages/category/helper/categoryIndexer.js +42 -11
- package/src-sample/pages/article/news-200910.md +13 -0
- package/src-sample/pages/article/tech-update.md +14 -0
- package/src-sample/pages/book/after-rain.md +13 -0
- package/src-sample/pages/book/japanese-history.md +13 -0
- package/src-sample/pages/book/modern-sculpture.md +13 -0
package/docs/spec.md
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
# @tenjuu99/blog — 技術仕様書
|
|
2
|
+
|
|
3
|
+
このドキュメントは `@tenjuu99/blog` の技術仕様を定義します。
|
|
4
|
+
|
|
5
|
+
## プロジェクト概要
|
|
6
|
+
|
|
7
|
+
`@tenjuu99/blog` は、Markdownファイルから静的HTMLサイトを生成するNode.js製の軽量静的サイトジェネレーターです。
|
|
8
|
+
|
|
9
|
+
### 主な特徴
|
|
10
|
+
|
|
11
|
+
- Markdownファイルベースのコンテンツ管理
|
|
12
|
+
- フロントマターによるメタデータ定義
|
|
13
|
+
- テンプレートエンジン機能(変数展開、条件分岐、スクリプト実行)
|
|
14
|
+
- ホットリロード対応の開発サーバー
|
|
15
|
+
- CSSの自動結合・minify・キャッシュバスト
|
|
16
|
+
- ヘルパー関数による拡張性
|
|
17
|
+
- パッケージシステムによる機能拡張
|
|
18
|
+
|
|
19
|
+
### 動作環境
|
|
20
|
+
|
|
21
|
+
- Node.js >= 21.7
|
|
22
|
+
- 依存パッケージ:
|
|
23
|
+
- `marked` ^13.x (Markdown → HTML変換)
|
|
24
|
+
- `chokidar` ^4.0.x (ファイル監視)
|
|
25
|
+
|
|
26
|
+
## CLIコマンド
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx create-blog # 新規プロジェクト作成
|
|
30
|
+
npx server # 開発サーバー起動(ホットリロード対応)
|
|
31
|
+
npx generate # 静的サイト生成
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ディレクトリ構成
|
|
35
|
+
|
|
36
|
+
### ユーザープロジェクト構成
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
project/
|
|
40
|
+
├── blog.json # 設定ファイル
|
|
41
|
+
├── src/ # ソースディレクトリ(デフォルト)
|
|
42
|
+
│ ├── pages/ # Markdownコンテンツ
|
|
43
|
+
│ ├── template/ # HTMLテンプレート
|
|
44
|
+
│ ├── css/ # スタイルシート
|
|
45
|
+
│ ├── image/ # 画像ファイル
|
|
46
|
+
│ ├── helper/ # ヘルパー関数
|
|
47
|
+
│ └── packages/ # カスタムパッケージ
|
|
48
|
+
├── dist/ # ビルド出力(デフォルト)
|
|
49
|
+
└── .cache/ # ビルド時キャッシュ(自動生成)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### ライブラリ内部構成
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
@tenjuu99/blog/
|
|
56
|
+
├── lib/ # コアライブラリ
|
|
57
|
+
├── bin/ # CLIコマンド
|
|
58
|
+
├── src-sample/ # サンプルプロジェクト
|
|
59
|
+
├── packages/ # コアパッケージ
|
|
60
|
+
│ ├── breadcrumbs # パンくずリスト
|
|
61
|
+
│ ├── editor # エディター機能
|
|
62
|
+
│ └── turbolink # Turbolink機能
|
|
63
|
+
└── index.js # エントリーポイント
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 設定ファイル (blog.json)
|
|
67
|
+
|
|
68
|
+
プロジェクトルートに `blog.json` を配置します。
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"site_name": "サイト名",
|
|
73
|
+
"url_base": "http://localhost:8000",
|
|
74
|
+
"src_dir": "src",
|
|
75
|
+
"dist_dir": "dist",
|
|
76
|
+
"distribute_raw": "image,js",
|
|
77
|
+
"helper": "index.js",
|
|
78
|
+
"packages": "breadcrumbs,turbolink",
|
|
79
|
+
"relative_path": "",
|
|
80
|
+
"allowedSrcExt": "md|html|txt"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 設定項目
|
|
85
|
+
|
|
86
|
+
| 項目 | デフォルト | 説明 |
|
|
87
|
+
|------|-----------|------|
|
|
88
|
+
| `site_name` | `"default"` | サイト名 |
|
|
89
|
+
| `url_base` | `"http://localhost:8000"` | ベースURL |
|
|
90
|
+
| `src_dir` | `"src"` | ソースディレクトリ |
|
|
91
|
+
| `dist_dir` | `"dist"` | 出力ディレクトリ |
|
|
92
|
+
| `distribute_raw` | `"image"` | そのままコピーするディレクトリ(カンマ区切り) |
|
|
93
|
+
| `helper` | `""` | ヘルパーファイル(カンマ区切り) |
|
|
94
|
+
| `packages` | `""` | 使用するパッケージ(カンマ区切り) |
|
|
95
|
+
| `relative_path` | `""` | 相対パス(サブディレクトリ配置時) |
|
|
96
|
+
| `allowedSrcExt` | `"md\|html\|txt"` | 処理対象の拡張子(正規表現) |
|
|
97
|
+
|
|
98
|
+
## フロントマター仕様
|
|
99
|
+
|
|
100
|
+
Markdownファイルの冒頭で `---` または `<!--` で囲んだ領域にメタデータを記述します。
|
|
101
|
+
|
|
102
|
+
### Markdown形式
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
---
|
|
106
|
+
title: ページタイトル
|
|
107
|
+
url: /custom-url
|
|
108
|
+
published: 2024-03-18
|
|
109
|
+
template: default.html
|
|
110
|
+
description: ページの説明
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
# コンテンツ
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### HTML形式
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<!--
|
|
120
|
+
title: ページタイトル
|
|
121
|
+
url: /custom-url
|
|
122
|
+
-->
|
|
123
|
+
<h1>コンテンツ</h1>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 組み込み変数
|
|
127
|
+
|
|
128
|
+
| 変数名 | デフォルト値 | 説明 |
|
|
129
|
+
|--------|-------------|------|
|
|
130
|
+
| `name` | ファイル名(拡張子なし) | ページの内部名 |
|
|
131
|
+
| `title` | `name` の値 | ページタイトル |
|
|
132
|
+
| `url` | `/${name}` | URLパス |
|
|
133
|
+
| `description` | 本文先頭150文字 | ページ説明 |
|
|
134
|
+
| `og_description` | `description` と同じ | OGP説明文 |
|
|
135
|
+
| `published` | `"1970-01-01"` | 公開日 |
|
|
136
|
+
| `preview` | `false` | プレビューモード(`true` で `/preview` 配下) |
|
|
137
|
+
| `index` | `true` | インデックスに含めるか |
|
|
138
|
+
| `noindex` | `false` | `noindex` メタタグ出力 |
|
|
139
|
+
| `lang` | `"ja"` | 言語コード |
|
|
140
|
+
| `distribute` | `true` | 配布対象とするか |
|
|
141
|
+
| `template` | `"default.html"` | 使用テンプレート |
|
|
142
|
+
| `ext` | `"html"` | 出力ファイル拡張子 |
|
|
143
|
+
| `site_name` | `config.site_name` | サイト名 |
|
|
144
|
+
| `url_base` | `config.url_base` | ベースURL |
|
|
145
|
+
| `relative_path` | `config.relative_path` | 相対パス |
|
|
146
|
+
| `markdown` | 解析後のHTML | Markdown変換後のHTML |
|
|
147
|
+
| `markdown_not_parsed` | フロントマター除去後 | 変換前のMarkdown |
|
|
148
|
+
| `full_url` | 自動生成 | 完全なURL |
|
|
149
|
+
| `__output` | 自動生成 | 出力ファイルパス |
|
|
150
|
+
| `__filetype` | 拡張子 | 元ファイルの拡張子 |
|
|
151
|
+
|
|
152
|
+
### データ型の記述
|
|
153
|
+
|
|
154
|
+
フロントマター内のデータ型:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
# 文字列
|
|
158
|
+
title: ページタイトル
|
|
159
|
+
|
|
160
|
+
# 数値
|
|
161
|
+
price: 1000
|
|
162
|
+
|
|
163
|
+
# 真偽値
|
|
164
|
+
published: true
|
|
165
|
+
|
|
166
|
+
# 配列(JSON形式)
|
|
167
|
+
tags: ["tag1", "tag2", "tag3"]
|
|
168
|
+
|
|
169
|
+
# オブジェクト(JSON形式)
|
|
170
|
+
metadata: {"author": "名前", "year": 2024}
|
|
171
|
+
|
|
172
|
+
# 複数行文字列(ダブルクォートで開始・終了)
|
|
173
|
+
description: "これは
|
|
174
|
+
複数行にわたる
|
|
175
|
+
説明文です"
|
|
176
|
+
|
|
177
|
+
# config参照
|
|
178
|
+
url_base: config.url_base
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**重要**: 配列・オブジェクトは `JSON.parse()` で解析されるため、有効なJSON形式である必要があります。
|
|
182
|
+
|
|
183
|
+
## テンプレート記法
|
|
184
|
+
|
|
185
|
+
`src/template/` 以下にHTMLテンプレートを配置します。
|
|
186
|
+
|
|
187
|
+
### 変数展開
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
{{ 変数名 }}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**重要**: 変数名は強制的に小文字化されます(`{{TITLE}}` も `{{title}}` として解釈)。
|
|
194
|
+
|
|
195
|
+
### エスケープ
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
\{{ 変数名 }} <!-- {{ 変数名 }} と出力される -->
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### includeディレクティブ
|
|
202
|
+
|
|
203
|
+
テンプレートやCSSファイルを読み込みます。
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
{include('template/header.html')}
|
|
207
|
+
{include('css/reset.css')}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- 再帰的にincludeを解決します
|
|
211
|
+
- キャッシュされるため同じファイルは1度だけ読み込まれます
|
|
212
|
+
|
|
213
|
+
### 条件分岐 (if)
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
{if 変数名}
|
|
217
|
+
表示される内容
|
|
218
|
+
{/if}
|
|
219
|
+
|
|
220
|
+
{if 変数名}
|
|
221
|
+
trueの場合
|
|
222
|
+
{else}
|
|
223
|
+
falseの場合
|
|
224
|
+
{/if}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### 比較演算子
|
|
228
|
+
|
|
229
|
+
```html
|
|
230
|
+
{if 変数A == 変数B} ... {/if}
|
|
231
|
+
{if 変数A != 変数B} ... {/if}
|
|
232
|
+
{if 変数A == "文字列"} ... {/if}
|
|
233
|
+
{if 変数A == 100} ... {/if}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### ヘルパー関数による条件
|
|
237
|
+
|
|
238
|
+
```html
|
|
239
|
+
{if ヘルパー関数名(引数)} ... {/if}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### スクリプト実行 (SSG)
|
|
243
|
+
|
|
244
|
+
ビルド時にJavaScriptを実行してHTML生成します。
|
|
245
|
+
|
|
246
|
+
```html
|
|
247
|
+
<script type="ssg">
|
|
248
|
+
return (new Date()).toString()
|
|
249
|
+
</script>
|
|
250
|
+
|
|
251
|
+
<!-- 短縮記法 -->
|
|
252
|
+
{script}
|
|
253
|
+
return variables.title.toUpperCase()
|
|
254
|
+
{/script}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### 利用可能なオブジェクト
|
|
258
|
+
|
|
259
|
+
- `variables`: 現在のページデータ(フロントマター + 組み込み変数)
|
|
260
|
+
- `helper`: ヘルパー関数オブジェクト
|
|
261
|
+
|
|
262
|
+
#### 注意点
|
|
263
|
+
|
|
264
|
+
- `return` で返した値がHTMLに展開されます
|
|
265
|
+
- `Promise` を返すこともできます(`async/await` 対応)
|
|
266
|
+
- `undefined` / `null` は空文字列として扱われます
|
|
267
|
+
|
|
268
|
+
### CSSジェネレーター記法
|
|
269
|
+
|
|
270
|
+
複数のCSSファイルを結合・minify・キャッシュバストします。
|
|
271
|
+
|
|
272
|
+
```html
|
|
273
|
+
<link rel="stylesheet" href="${/css/bundle.css<<reset.css,layout.css,page.css}">
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
↓ビルド時に次のように変換されます:
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<link rel="stylesheet" href="/css/bundle.css?t=a1b2c3d4e5f6...">
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- `${出力先<<元ファイル1,元ファイル2,...}` の形式
|
|
283
|
+
- MD5ハッシュによるキャッシュバスト
|
|
284
|
+
- `dist/css/` に結合・minifyされたCSSが出力されます
|
|
285
|
+
|
|
286
|
+
## ヘルパー関数
|
|
287
|
+
|
|
288
|
+
`src/helper/` 以下にJavaScriptファイルを配置し、`blog.json` の `helper` で指定します。
|
|
289
|
+
|
|
290
|
+
### 定義方法
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
// src/helper/index.js
|
|
294
|
+
import { allData, config } from '@tenjuu99/blog'
|
|
295
|
+
|
|
296
|
+
export function dateFormat(dateString) {
|
|
297
|
+
const date = new Date(dateString)
|
|
298
|
+
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function readIndex(filter = null) {
|
|
302
|
+
const data = Object.entries(allData)
|
|
303
|
+
.sort((a, b) => new Date(b[1].published) - new Date(a[1].published))
|
|
304
|
+
return filter
|
|
305
|
+
? data.filter(v => v[0].indexOf(filter) === 0).map(v => v[1])
|
|
306
|
+
: data.map(v => v[1])
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 使用方法
|
|
311
|
+
|
|
312
|
+
#### テンプレート内
|
|
313
|
+
|
|
314
|
+
```html
|
|
315
|
+
{{ dateFormat(published) }}
|
|
316
|
+
{{ readIndex('post') }}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### スクリプト内
|
|
320
|
+
|
|
321
|
+
```html
|
|
322
|
+
<script type="ssg">
|
|
323
|
+
const posts = helper.readIndex('post')
|
|
324
|
+
return posts.map(p => `<li>${p.title}</li>`).join('')
|
|
325
|
+
</script>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### `allData` オブジェクト
|
|
329
|
+
|
|
330
|
+
全ページデータを保持するオブジェクトです。
|
|
331
|
+
|
|
332
|
+
**キー形式**: 先頭スラッシュなしのパス(`pages/sample.md` → `sample`)
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
{
|
|
336
|
+
"sample": { title: "...", url: "/sample", ... },
|
|
337
|
+
"post/1": { title: "...", url: "/post/1", ... },
|
|
338
|
+
"post/2": { title: "...", url: "/post/2", ... }
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**注意**: `readIndex()` などでフィルタリングする際は先頭スラッシュを付けません。
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
// 正しい
|
|
346
|
+
helper.readIndex('post')
|
|
347
|
+
|
|
348
|
+
// 間違い(マッチしない)
|
|
349
|
+
helper.readIndex('/post')
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## パッケージシステム
|
|
353
|
+
|
|
354
|
+
コアパッケージまたはカスタムパッケージを有効化できます。
|
|
355
|
+
|
|
356
|
+
### コアパッケージ
|
|
357
|
+
|
|
358
|
+
`@tenjuu99/blog/packages/` に含まれるパッケージ:
|
|
359
|
+
|
|
360
|
+
- `breadcrumbs` - パンくずリスト機能
|
|
361
|
+
- `editor` - エディター機能
|
|
362
|
+
- `turbolink` - Turbolink機能
|
|
363
|
+
- `category` - カテゴリー機能(階層型カテゴリーページ自動生成)
|
|
364
|
+
|
|
365
|
+
#### category パッケージ詳細
|
|
366
|
+
|
|
367
|
+
階層型カテゴリーの自動ページ生成機能を提供します。
|
|
368
|
+
|
|
369
|
+
**基本的な使い方:**
|
|
370
|
+
|
|
371
|
+
1. ページのフロントマターでカテゴリーを指定:
|
|
372
|
+
```yaml
|
|
373
|
+
---
|
|
374
|
+
title: React入門
|
|
375
|
+
category: ["Tech", "Frontend", "React"]
|
|
376
|
+
---
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
2. `blog.json` で設定:
|
|
380
|
+
```json
|
|
381
|
+
{
|
|
382
|
+
"packages": "category",
|
|
383
|
+
"hooks": {
|
|
384
|
+
"afterIndexing": "categoryIndexer.js"
|
|
385
|
+
},
|
|
386
|
+
"category": {
|
|
387
|
+
"template": "category.html",
|
|
388
|
+
"auto_generate": true,
|
|
389
|
+
"max_depth": 3,
|
|
390
|
+
"url_case": "lower",
|
|
391
|
+
"url_separator": "-",
|
|
392
|
+
"url_prefix": ""
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
3. ビルド時に以下のページが自動生成されます:
|
|
398
|
+
- `/tech/index.html`
|
|
399
|
+
- `/tech/frontend/index.html`
|
|
400
|
+
- `/tech/frontend/react/index.html`
|
|
401
|
+
|
|
402
|
+
**スペース変換とURLプレフィックスの使用例:**
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"category": {
|
|
407
|
+
"url_case": "lower",
|
|
408
|
+
"url_separator": "-",
|
|
409
|
+
"url_prefix": "/books/category"
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
カテゴリー指定: `category: ["Contemporary Art", "Abstract Painting"]`
|
|
415
|
+
|
|
416
|
+
生成されるURL:
|
|
417
|
+
- `/books/category/contemporary-art/index.html`
|
|
418
|
+
- `/books/category/contemporary-art/abstract-painting/index.html`
|
|
419
|
+
|
|
420
|
+
**設定オプション:**
|
|
421
|
+
|
|
422
|
+
| オプション | 説明 | デフォルト |
|
|
423
|
+
|-----------|------|-----------|
|
|
424
|
+
| `name` | カテゴリーシステムの識別子(`categories` 配列使用時のみ) | - |
|
|
425
|
+
| `path_filter` | 対象とするページのパスプレフィックス(`categories` 配列使用時に推奨) | `""` (全ページ) |
|
|
426
|
+
| `template` | カテゴリーページのテンプレート | `category.html` |
|
|
427
|
+
| `auto_generate` | 自動生成の有効/無効 | `true` |
|
|
428
|
+
| `max_depth` | カテゴリーの最大階層数 | `3` |
|
|
429
|
+
| `url_case` | URLの大文字小文字変換 (`lower`/`original`) | `lower` |
|
|
430
|
+
| `url_separator` | スペースの置き換え文字 | `"-"` |
|
|
431
|
+
| `url_prefix` | カテゴリーURLのプレフィックス | `""` |
|
|
432
|
+
|
|
433
|
+
**設定オプションの詳細:**
|
|
434
|
+
|
|
435
|
+
- **`name`**: カテゴリーシステムの識別子。`categories` 配列で複数のカテゴリーシステムを定義する際に使用します。
|
|
436
|
+
|
|
437
|
+
- **`path_filter`**: 対象とするページのパスプレフィックス。
|
|
438
|
+
- 例: `path_filter: "book/"` → `book/` 配下のページのみがこのカテゴリーシステムに含まれる
|
|
439
|
+
- 空文字列の場合は全ページが対象
|
|
440
|
+
- 複数カテゴリーシステムを使用する場合は、各システムで `path_filter` を設定することを推奨
|
|
441
|
+
|
|
442
|
+
- **`url_separator`**: カテゴリー名にスペースが含まれる場合の置き換え文字を指定します。
|
|
443
|
+
- 例: `category: ["Contemporary Art"]` + `url_separator: "-"` → `/contemporary-art/`
|
|
444
|
+
- 例: `category: ["Contemporary Art"]` + `url_separator: "_"` → `/contemporary_art/`
|
|
445
|
+
|
|
446
|
+
- **`url_prefix`**: すべてのカテゴリーURLに共通のプレフィックスを追加します。
|
|
447
|
+
- 例: `url_prefix: "/blog/categories"` → カテゴリーページは `/blog/categories/tech/`、`/blog/categories/art/` など
|
|
448
|
+
- 空文字列の場合はルート直下に生成されます
|
|
449
|
+
|
|
450
|
+
**自動生成されるページのメタデータ:**
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
{
|
|
454
|
+
name: 'tech/frontend/index',
|
|
455
|
+
url: '/tech/frontend',
|
|
456
|
+
__output: '/tech/frontend/index.html',
|
|
457
|
+
title: 'Frontend', // カテゴリー名
|
|
458
|
+
template: 'category.html',
|
|
459
|
+
category_path: ['Tech', 'Frontend'], // カテゴリーパス
|
|
460
|
+
category_pages: ['tech/frontend/react/tutorial', ...], // このカテゴリーのページ
|
|
461
|
+
category_children: ['/tech/frontend/react', ...], // サブカテゴリー
|
|
462
|
+
__is_auto_category: true,
|
|
463
|
+
distribute: true
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**テンプレート内で利用可能な変数:**
|
|
468
|
+
|
|
469
|
+
- `{{title}}` - カテゴリー名
|
|
470
|
+
- `{{category_path}}` - カテゴリーパス配列
|
|
471
|
+
- `{{category_pages}}` - このカテゴリーに属するページ名の配列
|
|
472
|
+
- `{{category_children}}` - サブカテゴリーのURL配列
|
|
473
|
+
|
|
474
|
+
**複数カテゴリーシステムの使用:**
|
|
475
|
+
|
|
476
|
+
`categories` 配列を使用することで、複数の独立したカテゴリーシステムを定義できます。
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
{
|
|
480
|
+
"packages": "category",
|
|
481
|
+
"hooks": {
|
|
482
|
+
"afterIndexing": "categoryIndexer.js"
|
|
483
|
+
},
|
|
484
|
+
"categories": [
|
|
485
|
+
{
|
|
486
|
+
"name": "books",
|
|
487
|
+
"url_prefix": "/book-list",
|
|
488
|
+
"path_filter": "book/",
|
|
489
|
+
"template": "category.html",
|
|
490
|
+
"auto_generate": true,
|
|
491
|
+
"max_depth": 3,
|
|
492
|
+
"url_case": "lower",
|
|
493
|
+
"url_separator": "-"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"name": "articles",
|
|
497
|
+
"url_prefix": "/article-list",
|
|
498
|
+
"path_filter": "article/",
|
|
499
|
+
"template": "article-category.html",
|
|
500
|
+
"auto_generate": true,
|
|
501
|
+
"max_depth": 2
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**動作例:**
|
|
508
|
+
- `book/after-rain.md` (category: ["Art", "Painting"]) → `/book-list/art/painting/index.html`
|
|
509
|
+
- `article/news/200910.md` (category: ["News"]) → `/article-list/news/index.html`
|
|
510
|
+
- `/book-list/news/index.html` は生成**されない**(意図しないページ生成を防止)
|
|
511
|
+
- `/article-list/art/index.html` は生成**されない**(各システムは独立)
|
|
512
|
+
|
|
513
|
+
**後方互換性:**
|
|
514
|
+
|
|
515
|
+
既存の `category`(単数形)設定も引き続き動作します。`categories` 配列と `category` の両方が定義されている場合、`categories` が優先されます。
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"category": {
|
|
520
|
+
"template": "category.html",
|
|
521
|
+
"auto_generate": true
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**手動ページによる上書き:**
|
|
527
|
+
|
|
528
|
+
`src/pages/tech/index.md` が存在する場合、自動生成はスキップされます。
|
|
529
|
+
|
|
530
|
+
**ヘルパー関数:**
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
// カテゴリーツリーを取得
|
|
534
|
+
const tree = helper.getCategoryTree()
|
|
535
|
+
|
|
536
|
+
// 特定カテゴリーのページを取得(配列で指定)
|
|
537
|
+
const pages = helper.getCategoryPages(['Tech', 'Frontend'])
|
|
538
|
+
|
|
539
|
+
// サブカテゴリーを含む全ページを取得(配列で指定)
|
|
540
|
+
const allPages = helper.getCategoryPagesRecursive(['Tech'])
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### 有効化方法
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"packages": "breadcrumbs,turbolink"
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### パッケージの構成
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
packages/breadcrumbs/
|
|
555
|
+
├── breadcrumbs.js # ヘルパー関数(自動読み込み)
|
|
556
|
+
├── template/ # テンプレートファイル
|
|
557
|
+
└── css/ # スタイルシート
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
パッケージを有効化すると:
|
|
561
|
+
1. パッケージディレクトリが `.cache/` にコピーされる
|
|
562
|
+
2. `{パッケージ名}.js` が自動的に `helper` に追加される
|
|
563
|
+
|
|
564
|
+
## Hook機構
|
|
565
|
+
|
|
566
|
+
ビルドプロセスの特定のタイミングで、カスタムロジックを実行できるフック機構を提供しています。
|
|
567
|
+
|
|
568
|
+
### Hook設定
|
|
569
|
+
|
|
570
|
+
`blog.json` で設定:
|
|
571
|
+
|
|
572
|
+
```json
|
|
573
|
+
{
|
|
574
|
+
"hooks": {
|
|
575
|
+
"afterIndexing": "categoryIndexer.js"
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- **複数フック対応**: 配列で複数ファイル指定可能
|
|
581
|
+
```json
|
|
582
|
+
{
|
|
583
|
+
"hooks": {
|
|
584
|
+
"afterIndexing": ["hook1.js", "hook2.js"]
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### 利用可能なフックポイント
|
|
590
|
+
|
|
591
|
+
#### `afterIndexing`
|
|
592
|
+
|
|
593
|
+
全ページのインデックス化完了後、レンダリング開始前に実行されます。
|
|
594
|
+
|
|
595
|
+
**実行タイミング:**
|
|
596
|
+
```
|
|
597
|
+
indexing() → afterIndexing Hook → distribute()
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
**関数シグネチャ:**
|
|
601
|
+
```javascript
|
|
602
|
+
export async function afterIndexing(allData, config) {
|
|
603
|
+
// allData: 全ページデータ(参照渡し、変更可能)
|
|
604
|
+
// config: blog.json の設定内容
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**使用例:**
|
|
609
|
+
```javascript
|
|
610
|
+
// src/helper/categoryIndexer.js
|
|
611
|
+
export async function afterIndexing(allData, config) {
|
|
612
|
+
// カテゴリーページを自動生成
|
|
613
|
+
allData['tech/index'] = {
|
|
614
|
+
name: 'tech/index',
|
|
615
|
+
title: 'Tech',
|
|
616
|
+
template: 'category.html',
|
|
617
|
+
distribute: true,
|
|
618
|
+
// ... その他のメタデータ
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Hook関数の配置
|
|
624
|
+
|
|
625
|
+
- **配置場所**: `src/helper/` ディレクトリ
|
|
626
|
+
- **形式**: ES Module(`export` 必須)
|
|
627
|
+
- **実行順序**: 配列で指定した順に実行
|
|
628
|
+
|
|
629
|
+
### 注意事項
|
|
630
|
+
|
|
631
|
+
- フック関数は非同期(`async`)に対応
|
|
632
|
+
- `allData` は参照渡しのため、直接変更可能
|
|
633
|
+
- エラー発生時はビルドが中断される
|
|
634
|
+
- フックファイルが存在しない場合はスキップ(エラーにならない)
|
|
635
|
+
|
|
636
|
+
## ビルドプロセス
|
|
637
|
+
|
|
638
|
+
### 開発サーバー (`npx server`)
|
|
639
|
+
|
|
640
|
+
1. `.cache/` にソースをコピー(パッケージ含む)
|
|
641
|
+
2. テンプレートを事前読み込み(warmUp)
|
|
642
|
+
3. 全ページをインデックス化
|
|
643
|
+
4. 静的サイト生成
|
|
644
|
+
5. HTTPサーバー起動(ポート8000)
|
|
645
|
+
6. ファイル監視を開始
|
|
646
|
+
|
|
647
|
+
ファイル変更時:
|
|
648
|
+
- 該当ページのみ再生成(高速)
|
|
649
|
+
- テンプレート変更時は全ページ再生成
|
|
650
|
+
|
|
651
|
+
### 静的サイト生成 (`npx generate`)
|
|
652
|
+
|
|
653
|
+
1. `.cache/` にソースをコピー(パッケージ含む)
|
|
654
|
+
2. テンプレートを事前読み込み(warmUp)
|
|
655
|
+
3. 全ページをインデックス化(`lib/indexer.js`)
|
|
656
|
+
4. 各ページをレンダリング(`lib/render.js`)
|
|
657
|
+
- テンプレート読み込み
|
|
658
|
+
- フィルター処理(include, if, script)
|
|
659
|
+
- Markdown → HTML変換
|
|
660
|
+
- 変数展開
|
|
661
|
+
- HTML minify
|
|
662
|
+
5. `dist/` に出力
|
|
663
|
+
6. `distribute_raw` で指定したディレクトリをコピー
|
|
664
|
+
7. 削除されたページのファイルをクリーンアップ
|
|
665
|
+
|
|
666
|
+
## レンダリングパイプライン
|
|
667
|
+
|
|
668
|
+
各ページは以下の順序で処理されます:
|
|
669
|
+
|
|
670
|
+
### 1. ページデータ解析 (`lib/pageData.js`)
|
|
671
|
+
|
|
672
|
+
- フロントマターを抽出(`---...---` または `<!--...-->`)
|
|
673
|
+
- メタデータをパース(JSON対応)
|
|
674
|
+
- デフォルト値とマージ
|
|
675
|
+
|
|
676
|
+
### 2. テンプレート適用 (`lib/applyTemplate.js`)
|
|
677
|
+
|
|
678
|
+
- テンプレートファイル読み込み
|
|
679
|
+
- `include()` ディレクティブ解決
|
|
680
|
+
- CSSジェネレーター処理
|
|
681
|
+
|
|
682
|
+
### 3. フィルター処理(テンプレート) (`lib/filter.js`)
|
|
683
|
+
|
|
684
|
+
- `{if}` 条件分岐処理
|
|
685
|
+
- `{script}` / `<script type="ssg">` 実行
|
|
686
|
+
|
|
687
|
+
### 4. フィルター処理(Markdown) (`lib/render.js`)
|
|
688
|
+
|
|
689
|
+
- `include()` 解決
|
|
690
|
+
- `{if}` 処理
|
|
691
|
+
- `{script}` 実行
|
|
692
|
+
- 変数展開(`{{変数名}}`)
|
|
693
|
+
- Markdown → HTML変換(`.md` ファイルのみ)
|
|
694
|
+
|
|
695
|
+
### 5. 変数展開(テンプレート) (`lib/replaceVariablesFilter.js`)
|
|
696
|
+
|
|
697
|
+
- `{{変数名}}` を実際の値に置換
|
|
698
|
+
- ヘルパー関数実行(`{{関数名(引数)}}`)
|
|
699
|
+
|
|
700
|
+
### 6. 出力 (`lib/distribute.js`)
|
|
701
|
+
|
|
702
|
+
- HTML minify
|
|
703
|
+
- `dist/` に書き込み
|
|
704
|
+
|
|
705
|
+
## ファイル操作とキャッシュ
|
|
706
|
+
|
|
707
|
+
### `.cache/` ディレクトリの役割
|
|
708
|
+
|
|
709
|
+
#### 概要
|
|
710
|
+
|
|
711
|
+
`.cache/` はパッケージとユーザーコードを統合した「実効的なソースディレクトリ」として機能します。ビルドシステムはこのディレクトリを実際のソースとして扱います。
|
|
712
|
+
|
|
713
|
+
#### ファイル展開の仕組み
|
|
714
|
+
|
|
715
|
+
`lib/dir.js` の `cache()` 関数が以下の順序で実行します:
|
|
716
|
+
|
|
717
|
+
1. **コアパッケージの展開**: `packages/*/` → `.cache/`(名前空間フラット化)
|
|
718
|
+
2. **ユーザーパッケージの展開**: `src/packages/*/` → `.cache/`
|
|
719
|
+
3. **ユーザーコードのコピー**: `src/` → `.cache/`(上書き)
|
|
720
|
+
|
|
721
|
+
**展開例**:
|
|
722
|
+
```
|
|
723
|
+
packages/breadcrumbs/helper/breadcrumbs.js → .cache/helper/breadcrumbs.js
|
|
724
|
+
packages/breadcrumbs/css/breadcrumbs.css → .cache/css/breadcrumbs.css
|
|
725
|
+
packages/category/template/category.html → .cache/template/category.html
|
|
726
|
+
src/helper/custom.js → .cache/helper/custom.js(追加)
|
|
727
|
+
src/template/category.html → .cache/template/category.html(上書き)
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
#### ファイル優先順位
|
|
731
|
+
|
|
732
|
+
同名ファイルが存在する場合の優先順位:
|
|
733
|
+
|
|
734
|
+
1. **`src/`** (最優先 - ユーザーカスタム)
|
|
735
|
+
2. **`src/packages/`** (ユーザー定義パッケージ)
|
|
736
|
+
3. **`packages/`** (コアパッケージ)
|
|
737
|
+
|
|
738
|
+
この仕組みにより、ユーザーはパッケージが提供する任意のファイルを上書きしてカスタマイズできます。
|
|
739
|
+
|
|
740
|
+
#### Helper の自動登録
|
|
741
|
+
|
|
742
|
+
`blog.json` の `packages` で指定されたパッケージは:
|
|
743
|
+
- `.cache/helper/<package>.js` が自動的に `config.helper` に追加される(`lib/dir.js:38-40, 45-47`)
|
|
744
|
+
- ユーザーが明示的に設定する必要はない
|
|
745
|
+
|
|
746
|
+
#### ディレクトリ構造
|
|
747
|
+
|
|
748
|
+
ビルド時に以下がコピーされます:
|
|
749
|
+
|
|
750
|
+
```
|
|
751
|
+
.cache/
|
|
752
|
+
├── pages/ # src/pages/ のコピー
|
|
753
|
+
├── template/ # src/template/ + パッケージのテンプレート
|
|
754
|
+
├── css/ # src/css/ + パッケージのCSS
|
|
755
|
+
├── helper/ # src/helper/ + パッケージのヘルパー
|
|
756
|
+
├── image/ # src/image/ のコピー
|
|
757
|
+
└── index.json # ページインデックス(差分検出用)
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### テンプレートキャッシュ
|
|
761
|
+
|
|
762
|
+
`lib/applyTemplate.js` と `lib/files.js` により:
|
|
763
|
+
- 初回ビルド時に全テンプレート・CSSをメモリにロード
|
|
764
|
+
- 2回目以降はキャッシュから取得
|
|
765
|
+
|
|
766
|
+
### CSSキャッシュバスト
|
|
767
|
+
|
|
768
|
+
`lib/cssGenerator.js` により:
|
|
769
|
+
- CSS内容のMD5ハッシュを生成
|
|
770
|
+
- クエリパラメータとして付与(`?t={hash}`)
|
|
771
|
+
|
|
772
|
+
## URL生成ルール
|
|
773
|
+
|
|
774
|
+
### 基本ルール
|
|
775
|
+
|
|
776
|
+
| ファイルパス | デフォルトURL |
|
|
777
|
+
|-------------|--------------|
|
|
778
|
+
| `pages/sample.md` | `/sample` |
|
|
779
|
+
| `pages/post/1.md` | `/post/1` |
|
|
780
|
+
| `pages/index.md` | `/` |
|
|
781
|
+
|
|
782
|
+
### カスタムURL
|
|
783
|
+
|
|
784
|
+
フロントマターで `url` を指定:
|
|
785
|
+
|
|
786
|
+
```yaml
|
|
787
|
+
---
|
|
788
|
+
url: /custom-path
|
|
789
|
+
---
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### 拡張子の扱い
|
|
793
|
+
|
|
794
|
+
- `ext: "html"` (デフォルト): `/path` → `/path.html`
|
|
795
|
+
- `ext: "txt"`: `/path` → `/path.txt`
|
|
796
|
+
- `index.md`: `/index.html`
|
|
797
|
+
|
|
798
|
+
### プレビューモード
|
|
799
|
+
|
|
800
|
+
```yaml
|
|
801
|
+
---
|
|
802
|
+
preview: true
|
|
803
|
+
url: /article
|
|
804
|
+
---
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
→ `/preview/article` として出力され、`index: false`, `noindex: true` が自動設定されます。
|
|
808
|
+
|
|
809
|
+
## 開発サーバーの挙動
|
|
810
|
+
|
|
811
|
+
### ルーティング
|
|
812
|
+
|
|
813
|
+
1. `/path/` → `/path/index.html`
|
|
814
|
+
2. `/path` (拡張子なし) → `/path.html`
|
|
815
|
+
3. `/path.txt` → そのまま
|
|
816
|
+
4. 404の場合 → `/404.html` を返す
|
|
817
|
+
5. エラーの場合 → `/500.html` を返す
|
|
818
|
+
|
|
819
|
+
### MIMEタイプ判定
|
|
820
|
+
|
|
821
|
+
`lib/contentType.js` により拡張子からMIMEタイプを判定:
|
|
822
|
+
|
|
823
|
+
- `.html` → `text/html`
|
|
824
|
+
- `.css` → `text/css`
|
|
825
|
+
- `.js` → `text/javascript`
|
|
826
|
+
- `.jpg` → `image/jpeg`
|
|
827
|
+
- など
|
|
828
|
+
|
|
829
|
+
## エクスポートAPI
|
|
830
|
+
|
|
831
|
+
`index.js` から以下をエクスポート:
|
|
832
|
+
|
|
833
|
+
```javascript
|
|
834
|
+
import { allData, config, dir } from '@tenjuu99/blog'
|
|
835
|
+
|
|
836
|
+
// allData: 全ページデータ
|
|
837
|
+
// config: 設定オブジェクト
|
|
838
|
+
// dir: ディレクトリパス定義
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
これによりヘルパー関数から直接アクセスできます。
|
|
842
|
+
|
|
843
|
+
## 制約事項
|
|
844
|
+
|
|
845
|
+
- フロントマターは YAML 完全互換ではなく、独自パーサーを使用
|
|
846
|
+
- 配列・オブジェクトは JSON 形式で記述必須
|
|
847
|
+
- 変数名は強制的に小文字化される
|
|
848
|
+
- `include()` は同期処理のため、非同期ファイル読み込みは不可
|
|
849
|
+
- SSGスクリプト内で `import()` は使用不可(ヘルパー関数を使用)
|