Astro でも gatsby-plugin-mdx の excerpt を再現
2024/11/22
はじめに
Gatsby から Astro に乗り換えたのですが、 Gatsby では簡単に利用できていた記事本文の抜粋機能が Astro ではちょっと工夫しないと利用できなかったので、 対応内容をまとめてみました。
Gatsby で利用していた gatsby-plugin-mdx の excerpt 情報の再現方法についてです。
事前調査
gatsby-plugin-mdx における excerpt について
コードまでは追っていませんが、ドキュメントを読む限り、 内部的には rehype-infer-description-meta を利用しているとのことでした。
rehype-infer-description-meta 単体では、抽出した description を file.data.meta.description に格納するだけの rehype プラグインなので、
gatsby-plugin-mdx はおそらく、格納された結果を excerpt として参照できるようにしているのかと推測します。
Astro における frontmatter 加工について
Astro でも Gatsby と同様、frontmatter を利用できるわけですが、remark プラグイン、
もしくは rehype プラグインを通して加工することができます。
ドキュメントにサンプルも記載されていますが、
プラグイン内で file.data.astro.frontmatter に対して変更を加えることで、frontmatter を加工できるようです。
ただし、加工結果を取得する場合は、必ず render() メソッドを呼ぶ必要があります(ドキュメント)。
方針
rehype-infer-description-meta が生成した file.data.meta.description を file.data.astro.frontmatter.excerpt  に格納するだけのプラグインを自作する。
実装内容
rehype-infer-description-meta-astro-adapter.ts
import type { Root } from "hast";
import type { Plugin } from "unified";
const rehypeInferDescriptionMetaAstroAdapter: Plugin<[], Root> = () => {
  return (_, file) => {
    (
      file.data.astro as {
        // biome-ignore lint/suspicious/noExplicitAny:
        frontmatter: Record<string, any>;
      }
    ).frontmatter.excerpt = file.data.meta?.description ?? "";
  };
};
export default rehypeInferDescriptionMetaAstroAdapter;
astro.config.mjs
// @ts-check
import { defineConfig } from "astro/config";
import rehypeInferDescriptionMeta from "rehype-infer-description-meta";
import rehypeInferDescriptionMetaAstroAdapter from "./src/plugins/rehype-infer-description-meta-astro-adapter";
export default defineConfig({
  markdown: {
    rehypePlugins: [
      [rehypeInferDescriptionMeta, { truncateSize: 140 }],
      rehypeInferDescriptionMetaAstroAdapter,
    ],
  },
});
呼び出し例
export interface PostMeta {
  slug: CollectionEntry<"posts">["slug"];
  data: InferEntrySchema<"posts"> & {
    excerpt: string;
  };
}
export const getPostMetasAsync = async (options?: {
  filter?: (entry: CollectionEntry<"posts">) => boolean;
}): Promise<PostMeta[]> => {
  const filter = options?.filter;
  const posts = await getCollection("posts", filter);
  posts.sort((a, b) => {
    return b.data.date.valueOf() - a.data.date.valueOf();
  });
  return await Promise.all(
    posts.map(async (post) => {
      const { remarkPluginFrontmatter } = await post.render();
      const data: PostMeta["data"] = {
        title: remarkPluginFrontmatter.title,
        date: remarkPluginFrontmatter.date,
        tags: remarkPluginFrontmatter.tags,
        excerpt: remarkPluginFrontmatter.excerpt,
      };
      return {
        slug: post.slug,
        data,
      };
    }),
  );
};
おわりに
今回は gatsby-plugin-mdx に倣って rehype-infer-description-meta を利用したので、データ構造を合わせるためだけの rehype プラグインを自作しましたが、
やりたいこと的には remark プラグインの方があっていそうな気もしますし、色々な解決策がありそうですね。