Zenn
Closed14

astro-pagefind のコードリーディング

ikuma-tikuma-t
ikuma-tikuma-t

特徴

Build pagefind index upon static build
Serve previously prebuilt search index in astro dev mode
Search Astro component
Supports customized base URL path
Supports multiple instances of the component on a page
Supports pre-filled search query
Supports Astro view transitions

今回はこの中から、以下の3点を実現する部分を読んでいく。

  1. 静的ビルド時にインデックスを作成する
  2. dev mode で事前にインデックスを作成する
  3. Astroコンポーネントの提供

Pagefind 自体も一切読んでおらず、コンポーネントが core のラッパーなのか、それとも別途作っているものなのかわからないので、3から読もうと思う。

ikuma-tikuma-t

エントリポイントを見るために実際に使ってみる

Pagefind 自体は知っているが、実際に使ったことはないため、実際に使ってみる。
READMEを見る限り、Astro Integration として追加できる形式ではなさそうなので、Manual Install する。

pnpm add astro-pagefind
//astro.config.ts

import { defineConfig } from "astro/config";
import pagefind from "astro-pagefind";

export default defineConfig({
  build: {
    format: "file",
  },
  integrations: [pagefind()],
});

build のオプションは次のとおり。

https://docs.astro.build/ja/reference/configuration-reference/#buildformat

既存の検索機能の下に追加してみたものの、結果が出てこない。インデックスが作成されていない?

ikuma-tikuma-t

今回はコンポーネントがどんなものかわかればよかったので、動作検証はここで終了。

ikuma-tikuma-t

ビルド時・開発時のインデックス作成

Astro Integration は AstroIntegration 型のオブジェクトであれば登録することができる。AstroIntegration 型は、namehooks プロパティを持つ。

export interface AstroIntegration {
    /** The name of the integration. */
    name: string;
    /** The different hooks available to extend. */
    hooks: {
        'astro:config:setup'?: (options: {
            config: AstroConfig;
            command: 'dev' | 'build' | 'preview';
            isRestart: boolean;
            updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
            addRenderer: (renderer: AstroRenderer) => void;
            addWatchFile: (path: URL | string) => void;
            injectScript: (stage: InjectedScriptStage, content: string) => void;
            injectRoute: (injectRoute: InjectedRoute) => void;
            addClientDirective: (directive: ClientDirectiveConfig) => void;
            /**
             * @deprecated Use `addDevToolbarApp` instead.
             * TODO: Fully remove in Astro 5.0
             */
            addDevOverlayPlugin: (entrypoint: string) => void;
            addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void;
            addMiddleware: (mid: AstroIntegrationMiddleware) => void;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:config:done'?: (options: {
            config: AstroConfig;
            setAdapter: (adapter: AstroAdapter) => void;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:server:setup'?: (options: {
            server: vite.ViteDevServer;
            logger: AstroIntegrationLogger;
            toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
        }) => void | Promise<void>;
        'astro:server:start'?: (options: {
            address: AddressInfo;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:server:done'?: (options: {
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:build:ssr'?: (options: {
            manifest: SerializedSSRManifest;
            /**
             * This maps a {@link RouteData} to an {@link URL}, this URL represents
             * the physical file you should import.
             */
            entryPoints: Map<RouteData, URL>;
            /**
             * File path of the emitted middleware
             */
            middlewareEntryPoint: URL | undefined;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:build:start'?: (options: {
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:build:setup'?: (options: {
            vite: vite.InlineConfig;
            pages: Map<string, PageBuildData>;
            target: 'client' | 'server';
            updateConfig: (newConfig: vite.InlineConfig) => void;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:build:generated'?: (options: {
            dir: URL;
            logger: AstroIntegrationLogger;
        }) => void | Promise<void>;
        'astro:build:done'?: (options: {
            pages: {
                pathname: string;
            }[];
            dir: URL;
            routes: RouteData[];
            logger: AstroIntegrationLogger;
            cacheManifest: boolean;
        }) => void | Promise<void>;
    };
}
ikuma-tikuma-t

扱っている hooks は 次の 3 種類

hooks 概要
astro:config:setup Vite、Astro config の初期化前の段階
astro:server:setup Viteが dev mode で立ち上がった段階
astro:build:done production build が完了したタイミング

astro:config:setupではindexの出力先ディレクトリを指定し、astro:server:setupでは sirv を用いてビルド結果を配信。astro:build:donenpx pagefind --siteによりindexを生成、といった感じ。

ikuma-tikuma-t

ビルド完了時に行なっている処理を、ビルド後に pnpm dlx で実行したら検索結果が出るようになった。

pnpm dlx pagefind --site "./dist"

ikuma-tikuma-t

dev mode における挙動

今の所 dist にデータが出力されていないと検索ができないと思っているいるが、sirv の挙動を勘違いしている可能性もあるか?
→インデックスがない場合、動的に作るようなことはしない。

もう少し sirv について調べる。


https://github.com/lukeed/sirv/tree/master/packages/sirv

  • sirv(dir: string, opts?: { ... })のインターフェース
  • devオプションを true にすると、キャッシュをスキップして、すべてのリクエストごとにファイルシステムを走査する。

結局 outDir(デフォルトでは./dist)をみにいく構造上、一回インデックス作っていないとダメだな

ikuma-tikuma-t

dev modeでの動的なインデックス取得

Astro SSR モードでも同様の問題が起きるだろうと思ったら、Issue があった。

https://github.com/shishkin/astro-pagefind/issues/54

Pagefind の NodeJS API があり、そちらを使って動的にインデックスを作成するのが良いのでは、という話。

https://pagefind.app/docs/node-api/

ikuma-tikuma-t

なので、

  • dev mode では middleware を用いて、動的にリクエストを裁く
  • production mode ではビルドした結果の静的ファイルを参照する

というような仕組みが良さそう

このスクラップは2024/05/04にクローズされました
ログインするとコメントできます