Tags: Astro

Astro で mdx で記述したコンテンツの中身を plain text で取り出したい

(2023/07/16改定)Astro components を含んだ mdx で記述したコンテンツの中身を plain text として表示する方法について。
Astro 初学者が考えた方法なのでもっと簡単な方法がある可能性がある。

環境

  • Astro: v2.7.2
  • Content Collections 使用

やりたいこと

mdx で Astro Components を含むような記事を書いたとして

src/content/posts/20230701.mdx

---
title: "My First Blog Post"
date: 2022-06-26T20:43:03+09:00
description: "This is the first post of my new Astro blog."
tags: ["astro", "blogging", "learning in public"]
draft: false
---
import Header from "../../components/Header.astro";

# My First Blog Post

<Header />

Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website.

...

記事一覧ページに記事内容の冒頭だけ plain text で表示したい。

方法

Astro components を含んだ mdx であるため、一度 Astro の render 関数を通さないと正確な記事内容が得られないと考えた。 そのため Astro.slots.render() を使って HTML を生成後、記事内容部分だけ取り出して正規表現で HTML のタグを取り除くことにした。

PlainifyContent コンポーネント

<slot /> で受け取った記事全体の内容を plain text にして表示するコンポーネントを作る。 jsdom を使う。

src/components/PlainifyContent.astro

---
import { JSDOM } from "jsdom";

interface Props {
  /**
   * 指定の文字数だけ取得して他は捨てる
   * @default 300
   */
  truncate?: number;
}
const strhtml = await Astro.slots.render("default");
const dom = new JSDOM(strhtml);
const contenthtml = dom.window.document.body.innerHTML;
const { truncate = 300 } = Astro.props;
---
{contenthtml?.replace(/<[^>]+>/g, "").slice(0, truncate)}

参考

記事一覧ページ

記事一覧ページで PlainifyContent を使って記事内容を表示する。

src/pages/posts/[…page].astro

---
import type { GetStaticPathsOptions, Page } from "astro";
import { getCollection, type CollectionEntry } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";
import PlainifyContent from "../../components/PlainifyContent.astro";

type Props = {
  page: Page<CollectionEntry<"posts">>;
};

export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
  return paginate(await getCollection("posts"), { pageSize: 2 });
}

const { page } = Astro.props;
const renderedPages = await Promise.all(page.data.map(async (data) => {
  return {
    ...data,
    renderedContent: (await data.render()).Content
  }
}));
---

<BaseLayout title="記事一覧">
  <h1>記事一覧</h1>
  <ul>
    {
      page.data.map((data) => (
        <li>
          <a href={`/posts/${data.slug}/`}>
            <h2>{data.data.title}</h2>
	          <p>
              <PlainifyContent><data.renderedContent /></PlainifyContent>
            </p>
          </a>
        </li>
      ))
    }
  </ul>
</BaseLayout>

記事の内容は (await data.render()).Content で取り出せる。 これを renderedPages[].renderedContent に格納しておき、

<PlainifyContent><data.renderedContent /></PlainifyContent>

というように PlainifyContent に渡すことによって、PlainifyContent 側で色々やれるようになる。

以上