Astro + Markdocで、マークダウンファイルを取り込む
Astro2.1から追加された、Markdocインテグレーションを試してみました。 A new experimental Markdoc integration has also been published along […]
目次
Astro2.1から追加された、Markdocインテグレーションを試してみました。
A new experimental Markdoc integration has also been published alongside Astro 2.1.
https://astro.build/blog/astro-210/#markdoc-integration
Astroプロジェクトをセットアップ
まずはプロジェクトを立ち上げましょう。
% npm create astro@latest
Need to install the following packages:
  create-astro@latest
Ok to proceed? (y) 対話形式でセットアップします。
   dir   Where should we create your new project?
         ./astro-markdoc
  tmpl   How would you like to start your new project?
         ○ Include sample files 
         ● Use blog template 
         ○ Empty
  deps   Install dependencies?
         Yes
    ts   Do you plan to write TypeScript?
         ● Yes  ○ No 
   use   How strict should TypeScript be?
         ● Strict (recommended)
         ○ Strictest 
         ○ Relaxed 
   git   Initialize a new git repository? (optional)
         ● Yes  ○ No Markdocを追加する
astroコマンドで、Markdocを追加しましょう。
@astrojs/markdocがインストールされます。
% npx astro add markdoc
✔ Resolving packages...
  Astro will run the following command:
  If you skip this step, you can always run it yourself later
 ╭────────────────────────────╮
 │ yarn add @astrojs/markdoc  │
 ╰────────────────────────────╯
✔ Continue? … yesConfigファイルの変更も、CLIに任せることができます。
  Astro will make the following changes to your config file:
 ╭ astro.config.mjs ──────────────────────────────╮
 │ import { defineConfig } from 'astro/config';   │
 │ import mdx from '@astrojs/mdx';                │
 │ import sitemap from '@astrojs/sitemap';        │
 │                                                │
 │ import markdoc from "@astrojs/markdoc";        │
 │                                                │
 │ // https://astro.build/config                  │
 │ export default defineConfig({                  │
 │   site: 'https://example.com',                 │
 │   integrations: [mdx(), sitemap(), markdoc()]  │
 │ });                                            │
 ╰────────────────────────────────────────────────╯
? Continue? › (Y/n)次のメッセージが表示されれば完了です。
───────────────────────────╯
✔ Continue? … yes
  
   success  Added the following integration to your project:
  - @astrojs/markdocMarkdocでコンテンツを追加しよう
早速コンテンツを追加しましょう。
まずはディレクトリとファイルを配置します。
% mkdir src/content/docs
% touch src/content/docs/hello-world.mdoc
Markdoc公式サイトのサンプルを、hello-world.mdocに追加しました。
---
title: What is Markdoc?
---
# {% $markdoc.frontmatter.title %} {% #overview %}
Markdoc is a Markdown-based syntax and toolchain for creating custom documentation sites. Stripe created Markdoc to power [our public docs](http://stripe.com/docs).
{% callout type="check" %}
Markdoc is open-source—check out its [source](http://github.com/markdoc/markdoc) to see how it works.
{% /callout %}
## How is Markdoc different?
Markdoc uses a fully declarative approach to composition and flow control, where other solutions… [Read more](/docs/overview).
## Next steps
- [Install Markdoc](/docs/getting-started)
- [Explore the syntax](/docs/syntax)
{% $markdoc.frontmatter.title %}など、Markdoc独自の構文が含まれています。
今回のようなデモ・試してみた系には、ちょうどよさそうです。
Astrodえページを生成する
作成したファイルを元に、ページを生成しましょう。
まずはディレクトリとファイルを作成します。
% mkdir src/pages/docs 
% touch "src/pages/docs/[...slug].astro"
src/pages/docs/[...slug].astroにコンテンツの中身を表示するコードを追加します。
---
import { CollectionEntry, getCollection } from 'astro:content';
export async function getStaticPaths() {
    const posts = await getCollection('docs');
    return posts.map((post) => ({
        params: { slug: post.slug },
        props: post,
    }));
}
type Props = CollectionEntry<'docs'>;
const post = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
getStaticPathsで、getCollection('ディレクトリ名')からページに生成します。
この辺りは、Markdownやmdxを使う際とほぼ同じではないかと思います。
ローカルで動かしてみる
astro devを実行すると、mdocに記載した内容が描画されます。

Markdocでタグをカスタマイズする
ここまでだと、普通のMarkdownと変わり映えしないので、タグをカスタマイズしてみます。
src/components/Heading.astroを作成して、コードを追加しましょう。
<header id={Astro.props.id}>
    <h1 class={`level-${Astro.props.level}`}><slot></slot></h1>
</header>
astro.config.mjsで作成したタグとカスタマイズするMarkdocタグを関連づけます。
export default defineConfig({
  site: 'https://example.com',
  integrations: [mdx(), sitemap(), markdoc({
    nodes: {
      heading: {
        render: 'Heading',
        attributes: {
          level: {
            type: String
          }
        }
      }
    }
  })]
});
最後に、src/pages/docs/[...slug].astroでタグを読み込ませましょう。
---
import { CollectionEntry, getCollection } from 'astro:content';
import Heading from '../../components/Heading.astro'
export async function getStaticPaths() {
    const posts = await getCollection('docs');
    return posts.map((post) => ({
        params: { slug: post.slug },
        props: post,
    }));
}
type Props = CollectionEntry<'docs'>;
const post = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content
    components={{ Heading }}
/>これでカスタマイズできます。

独自タグや既存のMarkdown構文のカスタマイズに、活用しましょう。
おまけ: Cloudflare Pagesへデプロイ
Cloudflare Pagesにデプロイしてみましょう。
SSGとしてデプロイするため、Astroでビルドしてからデプロイします。
% npm run build
% npx wrangler pages publish dist新しいプロジェクトの作成や、既存プロジェクトへの連携が行えます。
No project selected. Would you like to create one or use an existing project?
❯ Create a new project
  Use an existing project
? Enter the name of your new project: › astro-markdoc
✔ Enter the production branch name: … mainデプロイが始まります。
POST /pages/assets/upload
POST /pages/assets/upload
POST /pages/assets/upload
result: null
result: null
result: null
🌍  Uploading... (20/20)
✨ Success! Uploaded 20 files (2.80 sec)
✨ Deployment complete! Take a peek over at https://xxxxx.astro-markdoc.pages.devアップロードできました。
これでAstroとMarkdocを利用したサイトを、Cloudflare Pagesでホストできます。
Markdownなどのファイル読み込み系は、SSR難しそうかも
AstroのSSRモードも、Cloudflare Pages(Workers)で試してみました。
次のようなエラーが出たため、現在はSSGでの利用が良いかもしれません。
 error   post.render is not a function
  File:
    /Users/sandbox/astro-markdoc/src/pages/docs/[...slug].astro:23:34
  Stacktrace:
TypeError: post.render is not a function
    at eval (/Users/sandbox/astro-markdoc/src/pages/docs/[...slug].astro:23:34) error   Could not resolve "module"
  File:
    dist/$server_build/_worker.mjs:10:7
  Code:
    9 | import 'string-width';
    > 10 | import 'module';
         |       ^
      11 | import 'node:fs/promises';
      12 | import 'node:url';
      13 | import 'html-escaper';Markdocではなく、「ファイルを読み込む処理」がWorkersで引っかかっているのかも・・・?