arXiv探訪

興味の赴くままに数学するだけ

GatsbyでMDXを試したけど案の定ド嵌りした

MDXというmarkdownの拡張みたいなものがあるらしいので使ってみました。次のますだらの更新で適用できたらいいなぁって感じです。Markdown中にコンポーネントを書けたり、その値を別のコンポーネントに持って行ったりできるので、自由度が高いのが売りみたいです。Markdown拡張自体はGatsbyプラグインでも似たようなことは出来るのですが、いまいちな感じでした。

それとは別に、markdown中のコンポーネント内部に数式を表示したい気持ちがあります。これが可能なら、例えば定理型環境や可換図式をmarkdownで記述できるので便利です。MDXで実現できるかは分かりませんが、やってみる価値はありそうです。

ということで始めた導入なんですが、ド嵌りしました。簡単に言うと、ドキュメント通りに作っても動きません。最終的にはなんとか表示まで漕ぎつけたのでここに載せときます。詳しい解説は、詳しい人がそのうち書いてくれると思います。

インストール

npm install gatsby-plugin-mdx @mdx-js/mdx@latest @mdx-js/react@latest

コード

  • ページ全体のレイアウトを定めるarticle-page-layout.jssrc/components以下に作る。
  • ページのコンテンツとなる.mdxファイルはcontent/posts以下に置くことにする。

gatsby-config.js

module.exports = {
    plugins: [
        {
            // Add support for *.mdx files in Gatsby
            resolve: `gatsby-plugin-mdx`,
            options: {
                extensions: [`.mdx`],
                defaultLayouts: {
                    default: require.resolve(`./src/components/article-page-layout.js`) 
                }
            },
        },
        {   // Add a collection called "posts" that looks for files in "content/posts"
            resolve: `gatsby-source-filesystem`,
            options: {
                name: `posts`,
                path: `${__dirname}/content/posts/`,
            },
        },
    ],
}

mdxtest.mdx

---
title: "MDXのテスト"
author: "mathmathniconico"
date: "2019-07-16"
---

# Hello MDX!

MDX IS FUCK.

gatsby-node.js

const path = require("path")
const { createFilePath } = require("gatsby-source-filesystem")

exports.onCreateNode = ({ node, actions, getNode }) => {
    const { createNodeField } = actions

    if (node.internal.type === "Mdx") {
        const value = createFilePath({ node, getNode })

        // Extend another node. The new node field is placed under the 'fields' key on the extended node object.
        createNodeField({
            // Name of the field adding
            name: "slug",
            // Individual MDX node
            node,
            // Generated value based on filepath.
            value: `/blog${value}`,
        })
    }
}

exports.createPages = ({ actions, graphql }) => {
    const { createPage } = actions
    
    return graphql(`
        {
            allMdx {
                edges {
                    node {
                        id
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `).then(result => {
        // Handling Errors.
        if (result.errors) {
            return Promise.reject(result.errors)
        }

        // Create pages.
        result.data.allMdx.edges.forEach(({ node }) => {
            createPage({
                path: node.fields.slug,
                component: path.resolve(`./src/components/article-page-layout.js`),
                context: {
                    // We can use the values in this context in our page layout component.
                    id: node.id
                },
            })
        })
    })
}

exports.createSchemaCustomization = ({ actions }) => {
    const { createTypes } = actions
    const typeDefs = `
        type Mdx implements Node {
            frontmatter: MdxFrontmatter
        }
        type MdxFrontmatter {
            author: String!
            date: Date
            tags: [String!]!
        }
    `
    createTypes(typeDefs);
}

article-page-layout.js

import React from 'react'
import { graphql } from 'gatsby'
import MDXRenderer from "gatsby-plugin-mdx/mdx-renderer"

export default function pageTemplate({ data: { mdx } }) {

    return (
        <div>
            <h1>{mdx.frontmatter.title}</h1>
            <MDXRenderer>{mdx.body}</MDXRenderer>
            <h2>{mdx.frontmatter.author}</h2>
        </div>
    )
}

export const pageQuery = graphql`
    query BlogPostQuery($id: String) {
        mdx(id: { eq: $id }) {
            id
            body
            frontmatter {
                title
                author
                date(formatString: "MMMM DD, YYYY")
            }
        }
    }
`

解説

とりあえず動いたってだけです。だいたいドキュメント通りに作ったのですが、嵌った理由は、mdx.bodyがドキュメントではmdx.code.bodyになってることです。仕様変更があったのでしょう。修正がプルリクに上がっていました。あとMdxのFrontmatterは最低限の機能しかないので、createSchemaCustomizationで追加する必要があります。

GraphQLの動きが良く分からない。引数とかpageQueryで指定した形に持ってくる、とかそういう感じなんでしょうか?

なお肝心のKaTeXが追加できるかは知りません。gatsby-remark-katexは動かないっぽいので、remark-mathrehype-katexを併用する形になると思いますが。

17日追記:できました。フォントを入れ忘れてただけでした。しかしコンポーネント内部に入れることはやはりできないので、残念です。