Scribe

アセット

field.asset()を使用して検証済みのアセットフィールドを宣言し、パスやフォーマットを制限しながら、読み込み時に公開URLとして解決します。

View as Markdown

Scribeは静的アセット(現在は画像)を、単なる文字列ではなく、スキーマで宣言されたファーストクラスの参照として扱います。中心となる原則は、コンポーネント内で文字列を結合してアセットのURLを構築することは決してないという点です。Frontmatterには正規のソース参照が保存され、ランタイムがロード時にそれを配信用のURLへと解決します。この間接化により、コードベースを移行することなく、publicPathやCDN、コンテンツハッシュ化を単なる設定として導入できるようになります。

設定

assets設定グループでは、ソースファイルの保存場所と配信方法を定義します。なお、単独のassetsDir{ dir: assetsDir }の非推奨エイリアスとして引き続き機能します。

export default defineConfig({
  // ...
  assets: {
    dir: "public",            // ソースファイルが配置されるディスク上のディレクトリ(rootDirからの相対パス)。デフォルトは "public"。
    publicPath: "/",          // 配信時のURLプレフィックス。パス("/static/"など)またはCDNのオリジンを指定。デフォルトは "/"。
    managedDirs: ["/blog-images"], // field.asset()に紐付かない、Scribe管理下の追加のWebパスルート。
  },
});

publicPathの概念は、webpackやViteの規則に準拠しています。managedDirsのエントリはソースルートとして機能します(MDX本文から参照されるディレクトリのうち、特定のアセットフィールドに紐付かないもの)。

field.asset()

import { field } from "scribe-cms";

const garmentSchema = z.object({
  title: field.translatable(z.string()),
  productImage: field.asset({
    dir: "/try-on/garments",   // 値はこのWebパス配下である必要があります
    formats: ["webp"],          // 許可する拡張子のリスト
    maxKB: 150,                 // ファイルサイズの制限(警告)
    optional: false,            // デフォルト値
  }),
});

field.relation()と同様に、制約はZodのメソッドチェーンではなくオプションオブジェクト内で定義します。チェーンをつなぐとスキーマが複製され、メタデータが失われてしまうためです。アセットフィールドは常に構造的です。アセットのパスが翻訳者に送られることはありません。

オプション意味
dirstring値が配置されるべきWebパスのプレフィックス。管理ルートとしての宣言も兼ねます。
templatestring派生パスのテンプレート(例: "/try-on/garments/{slug}/product.webp")。{slug}にはエントリの英語スラッグが入ります。これを設定すると、Frontmatterのフィールドを完全に省略でき、ローダーが自動で補完します。明示的に値を指定した場合はテンプレートよりも優先されます(エントリ間での意図的な共有に利用)。
formatsstring[]許可される拡張子(小文字、ドットなし)。違反すると検証警告が出ます。
maxKBnumberファイルサイズの上限。違反すると検証警告が出ます。
optionalbooleanフィールドを省略可能にします(templateがない場合のみ意味を持ちます)。値が存在しているのにファイルが見つからない場合は、引き続きエラーとなります。

Frontmatterの値は、assets.dirに対するルート相対のWebパスとなります(例: /try-on/garments/denim-flare/product.webp)。新しいURIスキームではなく、通常の画像文字列と同じ規則を使用します。ディスク上では、Frontmatterにソース参照が保存されます(テンプレート使用時は何も保存されません)。解決済みのURLはランタイムの出力結果にのみ存在し、静的エクスポートや生データのエクスポート、翻訳者には常に元のソース値が表示されます。

ローダーの解決処理

ランタイムはドキュメントをロードする際、スキーマをたどってアセットフィールドを探し、それぞれに対して以下の処理を行います。

  1. Frontmatterに値がなく、フィールドにtemplateが設定されている場合、テンプレートからパスを生成します({slug}は英語のスラッグに置き換わります)。
  2. 先頭にassets.publicPathを付与します(二重スラッシュにならないように結合されます。絶対オリジンのpublicPathもサポートされています)。

これにより、利用側は追加の呼び出しを行うことなく最終的なURLを取得できます。

const garment = scribe.garment.get("denim-flare");
garment.frontmatter.productImage;
// "/try-on/garments/denim-flare/product.webp"                       (publicPathが "/" の場合)
// "https://cdn.example.com/try-on/garments/denim-flare/product.webp" (CDNのpublicPathの場合)

この解決処理は、構造的フィールドが各ロケールのドキュメントにマージされた後に実行されます。そのため、すべてのロケールで英語ソースからの解決済み値を受け取ることができます。

scribe.assets.url(ref)

MDX本文内の画像やアドホックなケースのためのエスケープハッチです。生のリファレンスに対してpublicPathを適用します。

scribe.assets.url("/blog-images/hero.webp"); // publicPathが適用されます

また、assetインライントークンを使用して、MDX本文内でアセットを直接参照することも可能です。これも読み取り時に同様の方法で解決されます。

検証

scribe validateコマンドは、宣言されたアセットフィールドを検証します。

  • 必須アセットフィールドのファイル欠落: エラー(必須リレーションのリンク切れと同様に、ビルドがブロックされます)。
  • 値が存在するoptionalフィールドのファイル欠落: これもエラーとなります。optionalは「フィールド自体を省略できる」という意味であり、「指定されたパスが間違っていてもよい」という意味ではありません。
  • 値がフィールドのdirの範囲外: エラー。
  • 拡張子がformatsに含まれていない: 警告。
  • ファイルサイズがmaxKBを超過: 警告。
  • テンプレート化されたフィールドは、生成後のパスを検証します。Frontmatterで値が省略されていても、ファイルは存在している必要があります。

メッセージにはフィールドの属性情報が含まれます(例: garment/denim-flare: productImage → /try-on/garments/denim-flare/product.webp not found)。これとは別に、FrontmatterやMDX本文からヒューリスティックに収集された「画像らしき文字列」についても、見つからない場合は引き続き警告として報告されるため、移行作業なしで本文の画像もカバーできます。

Studio

Studioでは、アセットを読み取り専用で表示します。

  • エントリインスペクターでは、アセットフィールドが画像プレビューとしてレンダリングされ、解決済みのURL、ファイルサイズ、寸法が表示されます。ファイルが欠落している場合は赤いバッジが表示されます。
  • アセットブラウザ/assets)では、管理ルートごとにサムネイルグリッドが表示されます。各アセットにはパス、サイズ、寸法に加えて、それを参照しているすべてのエントリとフィールドのリスト("referenced by")が添えられます。未参照(孤立ファイルの候補)、参照されているが存在しない、サイズ超過、フォーマットの不一致などの状態は、バッジで警告されます。
  • ギャラリービューでは、アセットフィールドを持つ任意のタイプのエントリリストをカードグリッドとしてレンダリングでき、生成された画像の一覧をQA用に確認する画面として活用できます。
アセット · Scribe