設定不要のレジストリ:ゲーム追加はディレクトリ追加と同じ

一覧・詳細ルート・トップの注目枠・sitemap はすべて import.meta.glob が自動検出します。代償は Vite の規則、リテラルの相対パスだけという一点です。

このサイトにはゲームが 12 本、ツールが 20 個あります。33 個目を足したとき、設定ファイルは一行も触っていません。

契約は三行

各アプリは自己完結したディレクトリです。

src/games/<slug>/
  meta.ts    AppMeta をデフォルト export(三言語のタイトル/説明、order、featured)
  App.vue    Vue コンポーネントをデフォルト export
  logic/     DOM に触れない純粋ロジック

ディレクトリ名が slug、つまり URL のセグメントです。

レジストリの発見方法

const gameMeta = import.meta.glob('../games/*/meta.ts', { eager: true });

function collect(kind, modules) {
  return Object.entries(modules)
    .map(([path, mod]) => {
      const slug = path.split('/').slice(-2)[0];
      if (!mod.default) throw new Error('デフォルト export がありません: ' + path);
      return { kind, slug, ...mod.default };
    })
    .filter((entry) => !entry.draft)
    .sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
}

三点が重要です。slug はパスから取り、ファイル名以外の情報に頼りません。デフォルト export が無ければ例外を投げます。ビルドで落ちるほうが、カードが一枚欠けた状態で公開されるより良いからです。並び順は order だけで決まり、ファイルシステムの列挙順に依存しません。

一箇所を三箇所が使う

一覧ページ、詳細ルート、トップの注目枠は、同じ listApps(kind) から派生します。ルートは appDetailPaths(kind) が三言語分まとめて生成し、一覧は同じ配列からカードを作り、トップは featured の先頭三件を取ります。

出典が一つなら、「アプリを追加するときどこを直すか」の答えは「どこも」になります。

避けられない規則

Vite はビルド時にリテラルのパスしか静的に解析できません。したがって glob パターンはエイリアスにできず、変数にもできません。このプロジェクトで相対パスが必須になるのはちょうど二箇所で、もう一つは mount.ts にある App.vue のレジストリです。

代償

「設定不要」は契約が安定していることが前提です。あるアプリだけ追加のビルド時情報を必要としたら、それを meta.ts に積むのではなく、そもそも全アプリが持つべきフィールドなのかを考えるべきです。

← 記事一覧に戻る

コメント

…