Gatsby で MDX 記法の記事

Markdown による記事投稿

このサイトを始めるにあたり、記事は Markdown 記法で書きたかったので gatsby-plugin-mdx を導入した。Markdown 記法が使えればよかったのでチュートリアルにも出てきた gatsby-transformer-remark を使おうと考えていたが、MDX を使うと React コンポーネントを挿入できるということだったので優位性を感じ導入した。一部省略しているが下記のようなプラグイン設定を行っている。

gatsby-config.js
1{
2 plugins: [
3 {
4 resolve: `gatsby-plugin-mdx`,
5 options: {
6 defaultLayouts: {
7 posts: require.resolve("./src/layouts/post-layout.js"),
8 },
9 gatsbyRemarkPlugins: [
10 {
11 resolve: `gatsby-remark-images`,
12 options: {
13 maxWidth: 500,
14 },
15 },
16 ],
17 },
18 },
19 ]

gatsby-node.js の方も一部省略だが下記のようにして、記事ページに URL を持たせた。

gatsby-node.js
1const path = require(`path`)
2const { createFilePath } = require(`gatsby-source-filesystem`)
3
4exports.onCreateNode = ({ node, getNode, actions }) => {
5 const { createNodeField } = actions
6 if (node.internal.type === `Mdx`) {
7 const fileNode = getNode(node.parent)
8 createNodeField({
9 node,
10 name: `modifiedTime`,
11 value: fileNode.modifiedTime,
12 })
13 createNodeField({
14 node,
15 name: `birthTime`,
16 value: fileNode.birthTime,
17 })
18 const slug = createFilePath({ node, getNode, basePath: `posts` })
19 createNodeField({
20 node,
21 name: `slug`,
22 value: slug,
23 })
24 }
25}
26
27exports.createPages = async ({ graphql, actions }) => {
28 const { createPage } = actions
29 await createPostPages(graphql, createPage)
30}
31
32async function createPostPages(graphql, createPage) {
33 const result = await graphql(`
34 {
35 allMdx {
36 edges {
37 node {
38 fields {
39 slug
40 }
41 }
42 }
43 }
44 }
45 `)
46 result.data.allMdx.edges.forEach(( {node} ) => {
47 createPage({
48 path: node.fields.slug,
49 component: path.resolve(`./src/layouts/post-layout.js`),
50 context: {
51 slug: node.fields.slug,
52 }
53 })
54 })
55}

コードブロック

コードブロックには「言語ごとのハイライト・タイトル表示・行番号表示」が欲しかったので prism-react-renderer を導入し、更にコードブロック用のコンポーネントを作成した。

src/components/codeblock.js
1import React from 'react'
2import Highlight, {defaultProps} from 'prism-react-renderer'
3import theme from "prism-react-renderer/themes/oceanicNext";
4import * as styles from "./codeblock.module.css"
5
6
7export default function CodeBlock({children, className}) {
8 let [language, title] = (className || '').split(':');
9 language = language.replace(/language-/, '')
10 const CodeTitle = () => {
11 if (title) {
12 return (
13 <div className={styles.codeTitle}>
14 <span>{title}</span>
15 </div>
16 )
17 }
18 return (
19 <span></span>
20 )
21 }
22
23 return (
24 <Highlight {...defaultProps} theme={theme} code={children} language={language}>
25 {({ className, style, tokens, getLineProps, getTokenProps }) => {
26 tokens.pop()
27 return (
28 <div className={styles.codeBlockRoot}>
29 <CodeTitle />
30 <pre className={`${styles.codePre} ${className}`} style={style}>
31 {tokens.map((line, i) => {
32 const {
33 style: s,
34 className: c,
35 } = getLineProps({ line, key: i })
36 return (
37 <div key={i} style={s} className={`${styles.codeLine} ${c}`}>
38 <span className={styles.codeLineNumber}>{i + 1}</span>
39 <span className={styles.codeLineContent}>
40 {line.map((token, key) => (
41 <span key={key} {...getTokenProps({ token, key })} />
42 ))}
43 </span>
44 </div>
45 )
46 })}
47 </pre>
48 </div>
49 )
50 }}
51 </Highlight>
52 )
53}

そしたら今度はそのコンポーネントを MDX ファイルの中で使えるようにレイアウト側を編集する。こうすることでようやく Markdown のコードブロック記法で、ハイライト・タイトル・行番号の三拍子揃った表示ができるようになった。

src/layouts/posts-layout.js
1import React from "react"
2import { Link, graphql } from "gatsby"
3
4const components = {
5 pre: props => <div {...props} />,
6 code: CodeBlock,
7 Link,
8}
9
10export default function PostLayout ({ data }) {
11 return (
12 <MDXProvider
13 components={components}
14 >
15 <MDXRenderer>{data.mdx.body}</MDXRenderer>
16 </MDXProvider>
17 )
18}
19
20export const query = graphql`
21 query($slug: String!) {
22 mdx( fields: { slug: { eq: $slug } }) {
23 body
24 }
25 }
26`
© 2021 K.S.K. All rights reserved., Built with Gatsby