Skip to content

サイト生成#

View Markdown

ページ単位の Markdown 変換に加え、プラグインはドキュメントサイトが必要とするビルド単位の機能を載せます。静的 HTML 生成、ページごとの Open Graph 画像、コンテンツコレクション、生成 API ドキュメントです。

オプション 既定 目的
ssg { enabled: true } ビルド中に静的 HTML ページを生成する。
ogImage false ページごとの Open Graph 画像を生成する。
editThisPage false 「このページを編集」リンクを付ける。
collections content コレクション クライアントコードから Markdown を問い合わせる。
permalinks false frontmatter の permalink / slug URL。
cascade false ディレクトリ _index の既定 frontmatter。
docs { enabled: true } JSDoc / TSDoc から API ドキュメントを生成。
transformers [] 独自の Markdown AST 変換。

静的サイト生成#

SSG は既定でオンです。srcDir 以下のすべての Markdown が、既定テーマ、ナビ、検索 UI 付きの静的 HTML ページになります。いま読んでいるサイトも、まさにこの方法で生成しています。

import { defineConfig } from "vite-plus";
import { oxContent, defineTheme, defaultTheme } from "@ox-content/vite-plugin";

export default defineConfig({
  plugins: [
    oxContent({
      srcDir: "content",
      outDir: "dist/docs",
      ssg: {
        siteName: "Ox Content",
        siteUrl: "https://example.com",
        lastUpdated: true,
        theme: defineTheme({
          extends: defaultTheme,
          sidebar: [
            {
              text: "Guide",
              items: [{ text: "Getting Started", link: "/getting-started.md" }],
            },
          ],
        }),
      },
    }),
  ],
});
オプション 既定 目的
enabled true .md モジュールだけ残すときは ssg: false。ホスト側で コンポーネント CSS を import する。
extension ".html" 生成ページの拡張子。
routePrefix baseoutDir を変えずにページルートをマウントする。
transformConcurrency 1 決定的な render/write 段階の前に同時変換する Markdown ページ数。
clean false 書き出す前に生成物を消す。
bare false ナビなしの、テーマなし HTML を出す。
render 文書全体を所有する JSX コンポーネント。
lang "en" <html>lang 属性(bare モード)。
head <head> に足す生マークアップ(bare モード)。
bodyStart <body> の直後に足す生マークアップ(bare モード)。
bodyEnd </body> の直前に足す生マークアップ(bare モード)。
siteName <title> の接尾辞と OG サイト名。
siteUrl 絶対 OG URL、canonical、hreflang に使うオリジン。SEO
headValidation false 不正な head デスクリプタの warn / strictSEO
ogImage 静的フォールバックの OG 画像 URL。
generateOgImage false ページごとの OG 画像(後述)。
lastUpdated false ページごとの git 最終コミット時刻を出す。
contributors false ページごとの一意な git 作者。コントリビューター を見てください。
pagination false 記事のあとに前へ / 次へリンク。
breadcrumbs false サイトルートからサイドバー祖先までの道筋。
jsonLd false TechArticle / WebSite / BreadcrumbList の JSON-LD。
readerChrome false コピー、外部リンクアイコン、先頭へ戻る。
localeSwitcher false i18n ロケールがあるときのヘッダーロケールドロップダウン。
a11y false スキップリンクと印刷スタイル。
notFound false テーマ付き 404。 カスタム 404 を見てください。
team false layout: team のメンバーカード。チーム を見てください。
blog false ページ送り索引、著者、タグ、アーカイブ、任意の外部フィード。ブログ を見てください。
sectionIndex false index.md がないディレクトリ向けの生成一覧。セクション索引ページ を見てください。
pageChrome false ページ単位の frontmatter chrome フラグを尊重する。
markdownSource false 各ページの横に元の Markdown を公開する。Markdown ソースの併記
theme defaultTheme defineTheme() によるテーマ設定。
navigation 派生 ファイルツリーの代わりに明示的なナビグループ。

transformConcurrency は独立した Markdown 変換を重ねます。ビルド時の埋め込み fetch もここで重なります。値は整数に切り捨てられたあと 1..32 の範囲に制限されます。独自 transformer や埋め込み provider を直列に保つ既定値は 1 です。並行実行しても安全な場合にのみ 1 より大きい値を指定してください。独自 ssg.render テーマは collection のあと、決定的なページ render 段階で動きます。

ssg.routePrefix はデプロイの base やルートのホストファイルを動かさずに、Markdown ページルートを /blog のようなパスへマウントします。blog/blog/blog/ はどれも /blog 配下になります。ページ HTML とページ単位のアセットはプレフィックスに従い、_redirects_headers、ルートのフィード、サイトマップ index は outDir に残ります。base は公開 URL のプレフィックスのままです。パーマリンク がオンのとき、frontmatter の permalink が勝ちます。

oxContent({
  srcDir: "content/blog",
  outDir: "build",
  ssg: { routePrefix: "/blog" },
  redirects: { netlify: true },
  feeds: { path: "/" },
});

これは build/blog/first-post/index.html を書き、build/_redirectsbuild/feed.xml はデプロイルートに残します。

テーマ — 色、フォント、ヘッダー、フッター、サイドバー、独自 CSS、オプトインのページアウトライン(theme.aside、既定 false)— は別トピックです。テーマ を見てください。

パスと base#

サイト内のパスはサイトルート基準で書き、あとは base に任せます。ルート絶対パスは、書いた場所に関係なく base が前に付きます。Markdown のリンクや画像、theme.header.logo、エントリページの hero.image.src、フィーチャーのアイコン、どれも同じです。サブパスに配置したサイトでも、ルートに置いたサイトと同じように解決します。

oxContent({ base: "/team/docs/" });
[アーキテクチャ](/architecture/)

![アイコン](/img/icon.png)
<a href="/team/docs/architecture/">アーキテクチャ</a>
<img src="/team/docs/img/icon.png" alt="アイコン" />

値は書いたまま使います。すでに base で始まるパスにはもう一度 base が付くので、そのようには書かないでください。別オリジンの URL、プロトコル相対の // URL、#fragment だけのリンク、data:mailto: などスキームの付いたものはそのまま残します。サイトの外、本当にサーバルートにあるアセットは完全な URL で書いてください。

独自テーマコンポーネント#

ssg.render は文書全体を JSX コンポーネントに渡します。コンポーネントは <html> から下を所有するので、themebare、head メタデータのオプションは効きません。readerChrome を有効にしたときだけ、組み込み renderer と同じコードコピーと外部リンク変換を、描画済み文書へ後処理として適用します。

import { createTheme, usePageProps, useSiteConfig } from "@ox-content/vite-plugin";

function DefaultLayout({ children }) {
  const page = usePageProps();
  const site = useSiteConfig();
  return (
    <html lang="ja">
      <head>
        <title>{`${page.title} | ${site.name}`}</title>
        <link rel="stylesheet" href="/assets/site.css" />
      </head>
      <body>{children}</body>
    </html>
  );
}

oxContent({
  ssg: { render: createTheme({ layouts: { default: DefaultLayout } }) },
});

createTheme() は各ページの layout frontmatter が指すレイアウトを選び、なければ default です。レイアウト内では usePageProps() が現在ページを、useSiteConfig() がナビと他の全ページを含むサイト全体設定を返します。

これは組み込み JSX ランタイムを使うので、MDX と JSX のとおり jsxImportSource を設定してください。

Bare モード#

bare: true は、ナビ、レイアウトシェル、テーマスタイルなしで描画した Markdown 本文を出します。プロジェクトが独自のデザインシステムを持つとき、または JavaScript なしのベースラインを測るときに使います。

bare ページでも、プラグインがすでに計算している head メタデータは載ります。説明、og:*twitter:* タグ、canonical リンク、生成 OG 画像です。言うことがあるときだけ出ます。説明も siteUrl も OG 画像もないページは、bare モードが昔から出してきた最小文書そのものなので、サイズのベースラインは正直なままです。

それ以外は自分で注入します。

oxContent({
  ssg: {
    bare: true,
    lang: "ja",
    siteUrl: "https://example.com",
    head: '<link rel="stylesheet" href="/assets/site.css">',
    bodyStart: "<header>…</header><main>",
    bodyEnd: "</main><footer>…</footer>",
  },
});

siteUrl があると <link rel="canonical"> と絶対 og:url がオンになります。なければ推測せず、それらのタグを省きます。

bare モードでも readerChrome は使えます。有効にすると、描画済み本文に共有 reader-chrome 変換をかけ、対応する stylesheet と runtime を bare 文書へ注入します。

oxContent({
  ssg: {
    bare: true,
    readerChrome: { copy: true, externalLinks: false, backToTop: false },
  },
});

OG 画像#

ビルド時にページごとのソーシャルプレビュー画像を生成します。

oxContent({
  ogImage: true,
  ssg: {
    generateOgImage: true,
    siteUrl: "https://example.com",
  },
});

各ページはタイトルと説明から描画した画像を得ます。このページの生成画像は次のように見えます。

このページの生成 Open Graph 画像

サイトビルドなしで組み込みテンプレートだけ確認したいときは、oxct CLI から SVG を生成できます。

vpx oxct og-preview --title "My Docs" --description "Fast content tooling" --out og.svg
ogImageOptions 既定 目的
renderer "chromium" "chromium" はブラウザ CSS 互換、"satori" は高速な SVG 描画。
template 組み込み 独自テンプレート: .ts.vue.svelte.tsx
width 1200 画像幅(ピクセル)。
height 630 画像高さ(ピクセル)。
cache true 変わっていないページの再描画を飛ばす。
concurrency CPU 依存 並列の画像描画。既定は min(4, cores - 1)
satori.fonts [] Satori に渡すフォントファイル(.ttf.otf.woff)。
satori.systemFontFallback true satori.fonts が空のとき、よくある OS フォントパスを探す。

描画した画像は .cache/og-images にキャッシュします。キーはテンプレートのソースとページの props のハッシュなので、変更のないページは再描画せずキャッシュからコピーします。描画コストを払うのはタイトル・説明・frontmatter が変わったページだけです。このディレクトリは gitignore されているため、復元しない CI ジョブは毎回すべてのページを描画します。node_modules と同じように、実行間でキャッシュしてください。

Chromium は互換性重視のレンダラーです。通常のブラウザ CSS、ローカル public アセット、フレームワークテンプレートをページと同じ感覚で使えます。Satori は ブラウザを起動せず HTML を SVG にし、そこから PNG を作るので、大きいサイト ほど速くなります。その代わり、テンプレートは Satori が対応する HTML / CSS サブセットに収め、テキスト用のフォントを少なくとも 1 つ読み込める必要があります。

oxContent({
  ogImage: true,
  ogImageOptions: {
    renderer: "satori",
    satori: {
      fonts: [{ path: "public/fonts/Inter-Regular.ttf", name: "Inter" }],
    },
  },
  ssg: {
    generateOgImage: true,
    siteUrl: "https://example.com",
  },
});

bare では画像を 生成し、参照します。bare ページはテーマ付きページと同じ og:image / twitter:image タグを持ちます。buildSsg() はソースパスから画像 URL への ogImages マップも返すので、後処理が出力ツリーで og-image.png を探す必要はありません。

開発中は /__og-viewer が各ページの Open Graph メタデータと画像をプレビューします(ogViewer オプション、既定オン)。

開発中の OG ビューア

独自テンプレートはページ frontmatter を props として受け取ります。Custom OG Image Templates を見てください。

このページを編集#

すべてのページに「編集を提案」リンクを付けます。オプションは repoUrl を渡したときだけオンになります。裸の editThisPage: true はリンク先がないのでオフのままです。

oxContent({
  editThisPage: {
    repoUrl: "https://github.com/ubugeeei-prod/ox-content",
    branch: "main",
    label: "Edit this page",
  },
});

描画されたリンクは、そのページを作ったファイルを指します。

<p class="ox-edit-this-page">
  <a
    href="https://github.com/ubugeeei-prod/ox-content/edit/main/docs/content/example.md"
    target="_blank"
    rel="noopener noreferrer"
    >Edit this page</a
  >
</p>

既定ではページのパスはビルドを実行したディレクトリからの相対になります。通常の構成ではリポジトリのルートです。

ソースルートがリポジトリの別の場所(パッケージやドキュメント用のワークスペース)にあるときは rootDir を設定します。値はリポジトリの中で srcDir がある場所を表し、ページのパスは srcDir からの相対になります。

oxContent({
  srcDir: "docs",
  editThisPage: {
    repoUrl: "https://gitlab.example.com/owner/repo",
    branch: "main",
    rootDir: "packages/site/docs",
  },
});
<a href="https://gitlab.example.com/owner/repo/edit/main/packages/site/docs/guide/nested.md"></a>

他のフォージ#

フォージごとにウェブエディタのパスが違うため、別のフォージ向けのリンクは 404 になります。

Provider
github <repoUrl>/edit/<branch>/<path>
gitlab <repoUrl>/-/edit/<branch>/<path>
bitbucket <repoUrl>/src/<branch>/<path>?mode=edit
gitea <repoUrl>/_edit/<branch>/<path>

gitlab.combitbucket.orgcodeberg.orggitea.comrepoUrl から判別するので設定は要りません。自前でホストしている場合はホスト名からソフトウェアが分からないので provider を指定します。

oxContent({
  editThisPage: {
    repoUrl: "https://git.example.com/owner/repo",
    provider: "gitlab",
  },
});

gitea は同じパスを引き継いだ Forgejo も含みます。どれにも当てはまらないときは urlPattern で形そのものを差し替えます。{repoUrl}{branch}{path} を埋め、それ以外の波括弧はそのまま残します。

oxContent({
  editThisPage: {
    repoUrl: "https://git.example.com/owner/repo",
    urlPattern: "{repoUrl}/ui/edit?ref={branch}&file={path}",
  },
});

コレクション#

コレクションは Markdown ファイルを、遅延読み込み可能な問い合わせマニフェストとして出します。ブログ索引、変更履歴、「関連ページ」一覧に使えます。すべての Markdown を覆う既定の content コレクションは最初からあります。

import { queryCollection } from "virtual:ox-content/collections";

const guides = await queryCollection("content")
  .where("path", "LIKE", "/guide/%")
  .order("title", "ASC")
  .limit(10)
  .all();

クエリビルダ全体 — 演算子、グループ条件、frontmatter へのドットパス、select / order / limit — は専用ページです。コレクション を見てください。

API ドキュメント#

docs は JSDoc / TSDoc コメントから Markdown の API リファレンスを生成します。TypeScript 向けの cargo doc です。既定はオン(docs: false でオプトアウト)で、srcDir に書き出すので生成ページがサイトに混ざります。

oxContent({
  docs: {
    src: ["./src"],
    out: "content/api",
    include: ["**/*.ts"],
    exclude: ["**/*.test.*"],
    githubUrl: "https://github.com/owner/repo",
    generateNav: true,
  },
});

このサイトの API リファレンス は、プラグイン自身のソースからこのパイプラインで生成しています。エントリポイント、グループ化、ソート、リンクスタイル、種類ごとの描画形式を含むオプション一式は JSDoc から作る API ドキュメント です。

独自トランスフォーマ#

transformers はパースと描画のあいだに Markdown AST に対して走ります。ページ本文に置きたくない、プロジェクト固有の書き換え向けです。

import type { MarkdownTransformer } from "@ox-content/vite-plugin";

const stampDrafts: MarkdownTransformer = {
  name: "stamp-drafts",
  transform(ast, context) {
    if (context.frontmatter.draft) {
      ast.children.unshift({
        type: "paragraph",
        children: [{ type: "text", value: "🚧 Draft — not published yet." }],
      });
    }
    return ast;
  },
};

oxContent({
  transformers: [stampDrafts],
});

各トランスフォーマはパース済み AST と { filePath, frontmatter, options } を受け取り、(置き換えたかもしれない)AST を返します。async でも構いません。トランスフォーマは配列順で合成され、次のものは前のものの出力を受け取ります。

AST は remark プラグインが扱うのと同じ mdast です。フロントマターを解析し、オプトインの Markdown 機能を展開したあとの木が渡されます。描画、HTML の後処理、サニタイズ、目次はすべて最後のトランスフォーマが返した木に対して走るので、トランスフォーマが無い文書と、木をそのまま返す文書の出力は同じです。

例外を投げたトランスフォーマや、ノード以外を返したトランスフォーマはビルド警告として報告し、そのトランスフォーマだけを飛ばします。ページの残りはそのまま描画されます。

独自ホスト(ssg: false#

ページテンプレートを自分で持つホストでも、リソース指紋、Markdown 併記、 フィード、sitemap、git lastmod は再利用できます。planSsgOutputs と 対応する writer を呼んでください。SSG 出力プリミティブ を見てください。これらのフィールドを解決したいときは ssg: { enabled: false, markdownSource, lastUpdated, siteUrl } を使い、 boolean の ssg: false はそれらを消します。

関連#

Last updated: