LoginSignup
6
4

Astroでbuild.format: 'file'でもindex.htmlを生成するインテグレーション

Last updated at Posted at 2023-12-09

Astro v4.3 (2024/02/02更新)

Astro v4.3でbuild.format'preserve'が追加された。
'preserve'ではファイル構成のままビルドされる。

src/pages/about.astro -> dist/about.html
src/pages/about/index.astro -> dist/about/index.html

build.format: 'file'でindex.htmlを生成する

Astroはbuild.format'file'に設定することで各ページをディレクトリではなくHTMLファイルとしてビルドする。
しかしこの設定ではルート以外のindex.astroからindex.htmlが生成されない。Astroファイルと出力されるHTMLファイルの対応は以下のようになる。

src/pages/index.astro -> dist/index.html
src/pages/foo/index.astro -> dist/foo.html
src/pages/foo/bar.astro -> dist/foo/bar.html

src/pages/foo/index.astroからはdist/foo.htmlを生成したい、つまりpages以下のディレクトリ構造を保ったままHTMLを生成したいときのために、ビルド後のファイルをリネームするAstroインテグレーションを作った。

Astroインテグレーションのhookにはビルドが完了した後に実行されるastro:build:doneがある。
astro:build:doneroutesオプションで、生成されたルートとそのメタデータのリストが取得できるので、ここからインデックスルートのHTMLファイルを探し、リネームする。

環境

  • Astro: v4.0.3

該当するHTMLファイルを抽出

astro:build:doneroutesオプションは以下のプロパティを持つ。

  • route.type:ルートがHTMLページかそれ以外のエンドポイントかどうか。
  • route.component:元のファイルのパス。
  • route.pathname:出力されたページのパス。動的ルートではundefinedになる。

全ルートから/以外のindex.(astro|md|mdx|html)を抽出しリネームする。

ソースコード

import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { AstroIntegration } from "astro";

let shouldRename = false;

export default function createIntegration(): AstroIntegration {
  return {
    name: "astro-index-pages",
    hooks: {
      "astro:config:done": ({ config }) => {
        // build.format: 'file'の時のみリネームする
        if (config.build.format === "file") {
          shouldRename = true;
        }
      },
      "astro:build:done": async ({ dir, routes }) => {
        if (!shouldRename) {
          return;
        }

        const outDirPath = fileURLToPath(dir);

        await Promise.all(
          routes
            .filter((route) => {
              return (
                route.type === "page" &&
                route.pathname &&
                route.pathname !== "/" &&
                path.parse(route.component).name === "index"
              );
            })
            .map(async ({ pathname }) => {
              if (!pathname) return;
              // リネーム先のディレクトリパス
              const targetDirPath = path.join(outDirPath, pathname);
              // リネーム前のファイルパス
              const beforeFilePath = path.join(outDirPath, `${pathname}.html`);
              // リネーム先のファイルパス
              const afterFilePath = path.join(outDirPath, pathname, "index.html");
              await fs.mkdir(targetDirPath, { recursive: true });
              await fs.rename(beforeFilePath, afterFilePath);
            }),
        )
          .then(() => {
            logger.info('Success');
          })
          .catch((err) => {
            logger.error(err);
          });
      },
    },
  };
}

作成したインテグレーションを設定ファイルに追加する。

astro.config.mjs
import { defineConfig } from 'astro/config'
import indexPages from './astro-index-pages';

export default defineConfig({
  integrations: [
    indexPages()
  ]
})

生成されるHTMLファイルは以下になる。

src/pages/index.astro -> dist/index.html
src/pages/foo/index.astro -> dist/foo/index.html
src/pages/foo/bar.astro -> dist/foo/bar.html

おまけ

Write index routes as index.html with build.format: 'file' by matthewp · Pull Request #9209 · withastro/astroでインデックスルートを無視しないbuild.format: 'preserve'が考案されている。(まだDraftだが……)
このプルリクがマージされたらこのインテグレーションはいらなくなる。

→Astro v4.3でbuild.formatに'preserve'が追加された。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4