ちょっとした技術メモを忘れないうちに書いていく

GatsbyJS トランスフォーマープラグインを使用してマークダウンの内容を表示する

2019-06-20

GatsbyJSでマークダウンの内容を一覧表示と詳細表示、シンプルなブログっぽく


プロジェクトディレクトリ内のマークダウンを GraphQL で照会し、一覧画面と詳細画面を作成します。

準備:プラグインのインストールとマークダウンの作成

gatsby-transformer-remark のインストール

gatsby-source-filesystem も必要となるため、一緒にインストールします。

gatsby new gatsby-hello-world https://github.com/gatsbyjs/gatsby-starter-hello-world
npm install --save gatsby-source-filesystem gatsby-transformer-remark

次に、gatsby-config.js の plugins 配列に設定内容を記述します。 今回はプロジェクトディレクトリの下に data ディレクトリを作成し、ここにマークダウンファイルを配置するようにします。

gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/data/`,
      },
    },
    `gatsby-transformer-remark`,
  ],
}

GraphiQL(http://localhost:8000/\_\_\_graphq) にアクセスし、allMarkdownRemark と markdownRemark が表示されていることを確認します。

gatsby_markdown_1.png

マークダウンファイルの作成

data ディレクトリに file1.md、file2.md を作成します。 ファイルの最初に「-」に囲まれたスペースがあります。これは「frontmatter」と呼ばれ GraphQL から参照できます。

data/file1.md
---
path: "/file1"
date: "2019-06-16"
title: "マークダウンファイル その1"
---

## マークダウンファイルの内容
data/file2.md
---
path: "/file2"
date: "2019-06-19"
title: "マークダウンファイル その2"
---

+ 箇条書き
+ 箇条書き
+ 箇条書き

GraphQL でマークダウンファイルの内容を取得する

開発サーバーを再起動し、GraphiQL でマークダウンファイルの内容を取得します。 以下の内容の GraphQL を作成します。

  • frontmatter の date の昇順
  • frontmatter の内容を出力
  • マークダウンを変換した html
  • マークダウンファイル数(totalCount で取得する)
  • マークダウン内容の文字列部分の取得(excerpt で取得する)
query MyQuery {
  allMarkdownRemark(sort:
    	{
        fields: frontmatter___date,
        order: ASC
      }) {
    totalCount
    edges {
      node {
        frontmatter {
          title
          path
          date
        }
        html
        excerpt(format: PLAIN)
      }
    }
  }
}

実行結果にうまく表示されることを確認します。 html 部分の出力結果をみるとマークダウン内の「##」が「h2」、「+」が「li」に変換されています。 excerpt で指定している「format: PLAIN」は html 要素を抜いた文字列のみ取得できます(デフォルト)。「format: HTML」で html 要素つき、「format: MARKDOWN」でマークダウンの記述で取得できます。またデフォルトでは 140 文字数(html 要素含まない)ですが「pruneLength」を指定すると取得文字数を増減できます。 マークダウンのファイルは data ディレクトリ直下でなくても検索できるので、日付ディレクトリやカテゴリディレクトリを作成して、その配下にマークダウンを作成しても表示できます。

gatsby_markdown_2.png

GraphQL で取得した内容を画面に表示する

index.js にマークダウンの内容を一覧で表示します。マークダウンファイルの件数と frontmatter(date、title)、抜粋を表示します。

emotion で CSS を記述するのでインストールします。

npm install --save gatsby-plugin-emotion @emotion/core @emotion/styled
src/pages/index.js
// index.js
import React from "react"

// 1. graphqlのインポート
import { graphql } from "gatsby"
/** @jsx jsx */
import { jsx  } from '@emotion/core'

//emotionでそれっぽいスタイルを指定
const title = {
    fontSize: '24px'
}
const date = {
    marginRight: '16px',
    fontSize: '16px'
}
const excerpt = {
    marginTop: '8px',
    fontSize: '16px'
}
// 3. 2の結果がdataに自動的に渡されてくる
export default ({data}) => {
    //dataの内容を表示してみる
    console.log(data);
    return(
        <div>
        マークダウン数:{data.allMarkdownRemark.totalCount}{data.allMarkdownRemark.edges.map(({ node }, index) => (
        <div key={index}>
            <span css={date}>{node.frontmatter.date}</span>
            <span css={title}>{node.frontmatter.title}</span>
            <p css={excerpt}>{node.excerpt}</p>
        </div>
        ))}
        </div>
    )
}

// 2.マークダウン件数(totalCount)、frontmatter、内容の抜粋(excerpt)を取得する
export const query = graphql`
query MyQuery {
    allMarkdownRemark(sort: {fields: frontmatter___date, order: ASC}) {
      totalCount
      edges {
        node {
          frontmatter {
            title
            path
            date
          }
          excerpt
        }
      }
    }
  }
`

開発サーバーを再起動して、ブラウザで確認できます。

gatsby_markdown_3.png

マークダウンからページを生成する

次に、サーバー再起動時にマークダウンごとのページを作成します。

処理としては、 ページの元となるテンプレートに サーバ起動時 gatsby-node.js の処理で マークダウンファイルの内容を埋め込む感じとなります。

テンプレートの作成

マークダウンの内容を表示するテンプレートを「src/templates/blog.js」に作成します。 内容は GraphQL で title と html を取得し表示するページとなりますが、どのマークダウンか特定するために fields の slug が「$slug」と一致するという条件を指定します($slug は gatsby-node.js で作成)。

src/templates/blog.js
// blog.js
import React from "react"
import { graphql } from "gatsby"


export default ({ data }) => {
    const post = data.markdownRemark
    return (
        <div>
            <h1>{post.frontmatter.title}</h1>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
        </div>
        )
    }
// $slugに一致するマークダウンを取得
    export const query = graphql`
    query($slug: String!) {
        markdownRemark(fields: { slug: { eq: $slug } }) {
            html
            frontmatter {
                title
            }
        }
    }
`

gatsby-node.js で「slug」を設定する

さきほどテンプレートで記述した「slug」を GraphQL で使用できるようにします。 それには、ページを作成には gatsby-node.js を使用します。プロジェクトディレクトリ直下になければ作成し、onCreateNode 関数を記述します。 onCreateNode はノードが作成(または更新)されるタイミングで実行されます。 そのため、マークダウン以外のノードでも実行されてしまうので、マークダウンの時のみ処理できるようにします。

// gatsby-node.js
exports.onCreateNode = ({ node }) => {

    if (node.internal.type === `MarkdownRemark`) {
        //MarkdownRemarkのノードの時に表示
        console.log(node)
    }
}

開発サーバーの再起動を行うとコンソールに、マークダウンの node の内容が出力されます。

gatsby_markdown_4.png

今回は、マークダウンの frontmatter の path を URL に利用します。 path の内容を「slug」という名前でノードに追加します。

// gatsby-node.js
exports.onCreateNode = ({ node,actions }) => {
    if (node.internal.type === `MarkdownRemark`) {
        const { createNodeField } = actions
        // ノードに追加 fields配下に追加される
        createNodeField({
            node,//このノードに
            name: `slug`,//slugという名前で
            value: node.frontmatter.path//pathを設定
        })

    }
}

開発サーバーを再起動すると、GraphQL で「slug」が参照できます。

query MyQuery {
  allMarkdownRemark {
    edges {
      node {
        fields {
          slug
        }
      }
    }
  }
}

開発サーバーを再起動すると、新しく fields が追加され、createNodeField で追加した「slug」が表示されます。 この「slug」がさきほどテンプレートを作成した際に記述した「slug」になります。

gatsby_markdown_5.png

gatsby-node.js でページの生成

次に、ページの生成をします。 gatsby-node.js に onCreateNode を追加します、onCreateNode はノードの作成が完了した後に実行されます。

onCreateNode では以下の内容実装します。

  • テンプレートの絶対パスを取得
    • path.resolve で絶対パスの取得ができます(事前に require(path)で path を読み込んでおく)。
  • マークダウンの slug を取得
  • createPage でページの生成を行う
    • 引数に対応するパス、テンプレートの絶対パス、テンプレートに渡す引数を指定する
// gatsby-node.js
exports.onCreateNode = ({ node,actions }) => {
    if (node.internal.type === `MarkdownRemark`) {
        const { createNodeField } = actions
        // ノードに追加 fields配下に追加される
        createNodeField({
            node,//このノードに
            name: `slug`,//slugという名前で
            value: node.frontmatter.path//pathを設定
        })
    }
}

//ページ生成
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
    //テンプレートの絶対パスを取得
    const blogTemplate = path.resolve(`src/templates/blog.js`)
    const { createPage } = actions

    // マークダウンファイルのslugを取得
    return graphql(`
        {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `).then(
        result => {
            // 取得したslugの分ページを作成
            result.data.allMarkdownRemark.edges.forEach(edge  => {
                createPage({
                    path: edge.node.fields.slug,//このURLのとき
                    component: blogTemplate,//このテンプレートでページ生成
                    context: {
                        slug: edge.node.fields.slug,//slugという引数でedge.node.fields.slugを渡す
                    },
                })
            })
        }
    )
}

開発サーバーの再起動後、http://localhost:8001/file2 にアクセスすると マークダウンの内容が確認できます。

gatsby_markdown_6.png

一覧にリンクを設定

index.js の一覧表示から各ページへ遷移できるようにします。

  • GraphQL で fields.slug を取得
  • Link コンポーネントでリンクを作成し、遷移先に fields.slug を指定する
index.js
// index.js
import React from "react"

// 1. graphqlのインポート
import { Link,graphql } from "gatsby"
/** @jsx jsx */
import { jsx  } from '@emotion/core'

//emotionでそれっぽいスタイルを指定
const title = {
    fontSize: '24px'
}
const date = {
    marginRight: '16px',
    fontSize: '16px'
}
const excerpt = {
    marginTop: '8px',
    fontSize: '16px'
}
// 3. 2の結果がdataに自動的に渡されてくる
export default ({data}) => {
    //dataの内容を表示してみる
    console.log(data);
    return(
        <div>
        マークダウン数:{data.allMarkdownRemark.totalCount}{data.allMarkdownRemark.edges.map(({ node }, index) => (
        <div key={index}>
            <span css={date}>{node.frontmatter.date}</span>
            <Link to={node.fields.slug} css={title}>{node.frontmatter.title}</Link>
            <p css={excerpt}>{node.excerpt}</p>
        </div>
        ))}
        </div>
    )
}

// 2.マークダウン件数(totalCount)、frontmatter、内容の抜粋(excerpt)、fieldsのslugを取得する
export const query = graphql`
query MyQuery {
    allMarkdownRemark(sort: {fields: frontmatter___date, order: ASC}) {
      totalCount
      edges {
        node {
          frontmatter {
            title
            path
            date
          }
          excerpt
          fields {
            slug
          }
        }
      }
    }
  }
`