Hero img
Gatsby markdown解説

Gatsbyのmarkdownってどうなってるの?

Gatsby mdを理解してもらうためにgatsby-starter-blogを使いながら解説していきます。


目次

  • 目標
  • mdで記事ができる流れ
  • 記事ができるまで
  • gatsby-node.jsがやっていること
  • graphqlからデータを取得
  • ___graphqlで確認してみる
  • gatsby-node.jsでも確認
  • ページの作成
  • createPage
  • createPage?
  • path
  • component
  • context
  • blog-post.js
  • blog-post.jsの流れ
  • pageQuery
  • pageQueryがやっていること
  • 中身について
  • graphqlで使うためのお約束
  • 変数名を変更する場合
  • ブラウザーで確認
  • siteMetadata
  • markdownRemark
  • forntmatter
  • 前後の記事の取得
  • previousPostId
  • slugはどこから来たのか?
  • graphqlで取得したデータ利用する
  • markdownの振り返り
  • markdown応用
  • gatsby-nodeのデータを取得
  • 複数のタグを利用する
  • まとめ

目標

markdownを理解する。
markdownで記事ができる流れと、どこを変更すれば良いのか、その理解を深めて行きます。

mdで記事ができる流れ

Gatsbyでmd記事が作られて行く流れをまず解説していきます。
mdのシンプルなテンプレであるGatsby-starter-blogを使って行きます。

記事ができるまで

markdownを利用して、記事のページが作成されるまでの流れは、まず、ビルド時にmarkdownからページを生成しているのはgatsby-node.jsということを覚えて置いてください。
これがビルド時に起動し、graphqlからデーターを取得し、ページを作成しています。 具体的な流れは

  1. 1.gatsby-source-filesystemでmdのファイルたちをgraphqlに登録する。
  2. 2.graphql内のmdファイルをgatsby-transformer-remarkが検知して新たにallMarkdownRemarkのgraphqlを登録する。
  3. 3.gatsby-node.jsでallMarkdownRemarkを取得して、createPageを実行する。 createPageの実行時、markdownのidとその他の値を渡している。
  4. 4.blog-post.jsでidでフィルタリングを行いmarkdownの値等、必要な情報をpageQueryを使って、取得している。
  5. 5.pageQueryで指定してた値を暗黙的にpopsで取得しているのでそれらを利用する。
  6. 6.popsで得られた値を使ってHTMLの中身を作成していく。

gatsby-node.jsがやっていること

gatsby-node.jsが実際何をしてしているのか?

  1. 1.graphqlからデータを取得
  2. 2.markdown記事のテンプレを読み込み
  3. 3.テンプレにgraphqlから取得したmdデータを流し、ページを作成

graphqlからデータを取得

./gatsby-node.js
//20行
  const result = await graphql(`
    {
      allMarkdownRemark(sort: { frontmatter: { date: ASC } }, limit: 1000) {
        nodes {
          id
          fields {
            slug
          }
        }
      }
    }
  `)

// 41行
const posts = result.data.allMarkdownRemark.nodes

このコードでは、graphqlからデータを取得しpostsに入れています。 ここで取得している物はidとslugになっています。

___graphqlで確認してみる

ブラウザーを開きgraphqlを確認してみます。

gatsby develop

ブラウザーでqraphqlの確認

ブラウザーでhttp://localhost:8000/___graphqlにアクセスします。 gatsby-markdown-qraphql

クエリーの記述

クエリーは以下の通りです。

query MyQuery {
  allMarkdownRemark(sort: { frontmatter: { date: ASC } }, limit: 1000) {
  nodes {
          id
          fields {
            slug
          }
        }
      }
    }

クエリーの結果

クエリーの結果はこれが返って来ます。

{
  "data": {
    "allMarkdownRemark": {
      "nodes": [
        {
          "id": "2074e68e-bdb3-5b8a-a292-44f2f622e6d3",
          "fields": {
            "slug": "/hello-world/"
          }
        },
        {
          "id": "7a3f41a6-12ca-5060-af3d-01e636b95483",
          "fields": {
            "slug": "/my-second-post/"
          }
        },
        {
          "id": "039cb5fe-73d7-5867-8130-bbd79ef7edc0",
          "fields": {
            "slug": "/new-beginnings/"
          }
        }
      ]
    }
  },
  "extensions": {}
}

gatsby-node.jsでも確認

gatsby-node.jsのpostsに入っている値はブラウザーで確認したものと同じ結果になります。

console.logで確認

console.logでも確認してみます。

gatsby-node.js
//41行目
  const posts = result.data.allMarkdownRemark.nodes
  console.log(posts) //<--追加

以下結果 gatsby-markdown-log

オブジェクトやら、配列やら違いはありますが、同じ値が返って来ています。

ページの作成

先ほどのpostsのデーターをforEachでループさせ、createPageを実行しています。 つまりここでmarkdownファイルからページの作成を行っています。

gatsby-node.js
//49行目
    posts.forEach((post, index) => {
      const previousPostId = index === 0 ? null : posts[index - 1].id
      const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id

      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })
    })

createPage

ページの作成はこのcretePageです。
※注意createPageではmdのデータをまだ取得していません。

gatsby-node.js
      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })

createPage?

createPageはページの作成を行っています。色々と書かれていてややこしいです。 createapi公式  

これらは何を意味しているのか。

path

ページのパスを指定している。
実際の値は「/hello-world/」

component

テンプレートを指定している。
このgatsby-stater-blogではblog-post.jsのことです。
blog-post.jsは記事レイアウト等jsxが書かれています。

context

idとその他データを渡している。
ここに独自の値も追加できるので必要な物があればここに追加する。
それに伴い、テンプレートも変更が必要になる場合も。

idがなぜ必要?

path,componentは理解できるが、idがなぜ必要かというと、 blog-post.jsの方でidでフィルタ―を行い、markdownのデータを取得しているからです。
mdのデータの取得はblog-post.jsがしています。
そのため仮に、contextに記事の内容(HTML)等全部入れるとidが無くてもページの作成ができます。

blog-post.js

テンプレファイルであるblog-posts.jsが実際に何をしているのかを解説していきます。 ここでのポイントはblog-post.jsの一番下のpageQueryの部分です。

blog-post.jsの流れ

このテンプレは実際に何をしているのか?順を追って説明します。

  1. 1.pageQueryを使いgraphqlのmarkdownのデータを取得する。
  2. 2.markdownのデータはdataに入って来るのでそれを利用する。
  3. 3.jsxに書かれた内容にdataを流し込まれHTMLが作成される。

pageQuery

markdownを使うにあたってここが最難関だと思っています。
私自身ここの記述が理解できなかったのでgatsbyを諦めようと何度も思いました。
そして、gatsbyの日本語の記事が少なすぎる...

pageQueryがやっていること

ここでもまたqraphqlが登場します。orz...
上から順に解説していきます。

blog-post.js
export const pageQuery = graphql`
  query BlogPostBySlug(
    $id: String!
    $previousPostId: String
    $nextPostId: String
  )

関数のエクスポート

このexport const pageQuery = graphqlはgraphqlを使う時のテンプレだと理解して頂いても問題ありません。 このpageQuery名は特に指定が無いようなので。 export const ABCD = graphqlとしても動きます。変更する必要性もないのでそのままで良いです。

クエリ―

このBlogPostBySlugも名前はなんでも構いません。
一点注意事項としては複数のテンプレを使用する際この名前は被らないようにしてください。被るとエラーが発生します。

中身について

BlogPostBySlugの中身に以下の記述があります。

$id: String!  
$previousPostId: String  
$nextPostId: String  

これはgatsby-node.jsの部分でcontextのデータを送った物を受け取るために必要な記述です。

gatsby-node.jsで指定したもの

gatsby-node.js
createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })

graphqlで使うためのお約束

graphql内で使うためには$変数名:型を指定します。
また、この変数名はgatsby-node.jsで指定したものと全く同じ名前で指定してください。 また、大文字小文字の区別もするため変数名はご注意ください。型も大文字小文字に注意してください。

変数名を変更する場合

gatsby-node.jsで変数名を指定していますが、その名前を変更する場合はこのようにしてください。

変数名を変更例

gatsby-node.js
id: post.id,
servalue , //通常こんな感じ
hoge: post.setvalue//hogeにしたい場合

受け取り側

blog-post.js
$id: String!
$servalue: String
$hoge:String

graphqlからデータを取るコード

次に、ここから下のコードが実際にmdのデータ取得しています。

blog-post.js
 {
    site {
      siteMetadata {
        title
      }
    }
    markdownRemark(id: { eq: $id }) {
      id
      excerpt(pruneLength: 160)
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        description
      }
    }

ブラウザーで確認

ブラウザーの方で確認してみます。 gatsby-markdown-qraphql2 ブラウザーではこのようなデータを取ることができました。なんの値が取れるのかを確認するためにはブラウザーで確認するのが早いです。

siteMetadata

siteMetadataはgatsby-config.jsに記述されています。難しいものはありません。

markdownRemark

ようやくmdファイルのデータをここで取ることができます。

blog-post.js
    markdownRemark(id: { eq: $id }) {
      id // id 
      excerpt(pruneLength: 160)
      html //mdの記事の本体
      frontmatter {//mdファイルの一番上の部分
        title
        date(formatString: "MMMM DD, YYYY")
        description
      }
    }

ここでhtmlが出てきました。
このhtmlがmdの中身、HTMLの本体です

forntmatter

forntmatterも結構重要な部分です。
mdファイルの中身一番上に書かれています。

./content/blog/hello-world/index.md
---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
description: "Hello World"
---

このように、この記事独自の情報を入れておくことで可変的に変更が容易になります。

前後の記事の取得

blog-post.jsのpageQueryの部分ラストです。

blog-post.js
previous: markdownRemark(id: { eq: $previousPostId }) {
      fields {  
        slug
      }
      frontmatter {
        title
      }
    }
    next: markdownRemark(id: { eq: $nextPostId }) {
      fields {
        slug
      }
      frontmatter {
        title
      }
    }

この「previousPostId,nextPostId」値はgatsby-node.jsの変数を確認してみてください。

コードを見る

gatsby-node.jsの変数を確認

gatsby-node.js
    posts.forEach((post, index) => {
      const previousPostId = index === 0 ? null : posts[index - 1].id
      const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id

となっています。つまり現在の記事postsの前後に記事があればその記事のidを渡しています。

previousPostId

変数名の通りですがpreviousPostId=前の記事のIdです。
ここでもpreviousと言う変数名に変換しています。

blog-post.js
previous: markdownRemark(id: { eq: $previousPostId }) {//名前をpreviousに変更
      fields {  
        slug//"slug": "/hello-world/"
      }
      frontmatter {
        title//index.mdのtitle: hellow Worldを取得している。
      }
    }

slugはどこから来たのか?

gatsby-node.jsでcreateNodeFieldというものを使ってgraphqlに値を追加しています。

gatsby-node.js
//70目
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })

    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

以上でblog-post.jsのgraphqの部分の説明が終わりました。相当長い...

graphqlで取得したデータ利用する

ここからは実際にデータが取れたのでそのデータを使用してテンプレに流して行きます。

graphqlデータの取得

./templates/blog-post.js
//8行目
const BlogPostTemplate = ({
  data: { previous, next, site, markdownRemark: post },
  location,
}) => {
   return (

graphqlで取得したデータは自動的に第一引数にporpsとして渡されます。
porpsは全てのデータをもっていますが、このテンプレでは不要なのでこのような書き方になっているようです。

このコードを崩すと次のようになります。

コードを崩す

./templates/blog-post.js
const BlogPostTemplate = (props) => {
  const previous = props.data.previous
  const next = props.data.next
  const site = props.data.site
  const post = props.data.markdownRemark
  const location = props.location
  const siteTitle = site.siteMetadata?.title || `Title`
   return (

第一引数以外は何もかえって来ないようなので以下のようにしても二つ目以降は何もデータがありません。

ダメな例1

引数2は空

./templates/blog-post.js
const BlogPostTemplate = (props,otherdata) => {
  const previous = props.data.previous
  const next = props.data.next
  //中身がないのでotherdata = {}です。

   return (

ダメな例2

以下のようにしても何も返って来ません。

./templates/blog-post.js
const BlogPostTemplate = (props,{data: {site}}) => {
  //propsはたくさんデータを持っています。
  //siteは二つ目の引数なので= {}です。
   return (

データを実際に使う

あとはただデータを流し込むだけなので難しくありません。

./templates/blog-post.js
          <h1 itemProp="headline">{post.frontmatter.title}</h1>
          <p>{post.frontmatter.description}</p>

mdの展開

mdのデータを展開するにはdangerouslySetInnerHTMLを使う必要があります。

./templates/blog-post.js
        <section
          dangerouslySetInnerHTML={{ __html: post.html }}
          itemProp="articleBody"
        />

post.htmlとはmarkdownRemarkでhtmlと書いたあのhtmlの事です。

markdownの振り返り

  1. 1.markdownを使う場合はgatsby-node.jsをまず触る必要がある。
  2. 2.gatsby-node.jsではmdをqraphqlで取得し、createPageを実行
  3. 3.createPageで「固有のidとページのpath,使用するテンプレ、その他データ」を渡し、ページを作成
  4. 4.blog-post.jsで、送られてきたデータ「id」からフィルタ―しmdのデータを取得。
  5. 5.そのデータ利用しdangerouslySetInnerHTMLで展開する。

markdown応用

gatsby-nodeのデータを取得

markdown記述ではgatsby-node.jsの独自の値をblog-post.jsに渡す事ができます。

gatsby-node.jsでkasutamuを渡す

gatsby-node.js
//47行目
  var customvalue = "kasutamu"//<--追加
  if (posts.length > 0) {
    posts.forEach((post, index) => {
      const previousPostId = index === 0 ? null : posts[index - 1].id
      const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id

      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          customvalue,//<--追加
          previousPostId,
          nextPostId,
        },
      })
    })
  }
}

blog-post.jsでkasutamuを受け取る

./src/templates/blog-post.js
const BlogPostTemplate = (props ,others) => {
  const previous = props.data.previous
  const next = props.data.next
  const site = props.data.site
  const post = props.data.markdownRemark
  const location = props.location
  const siteTitle = site.siteMetadata?.title || `Title`
  console.log(props.pageContext)

コンソールログ画面

gatsby-markdown-get-value

複数のタグを利用する

タグ等で複数の値を追加したい場合mdでは以下のように記入します。

複数のtag

---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
description: "Hello World"
tag : 
- A
- B
- B
---

ブラウザーで確認

ブラウザーでtagが配列で返って来ることを確認できました。 gatsby-multi-frontmatter

まとめ

Gatsbyはこの部分が非常に難しい。markdownでただ書きたいだけなのに、それがとんでもなく大変でした。ここをある程度理解できると、プラグイン無しでも実装が可能になる...かもしれない。
markdownが理解できたらmdxを使う事も容易くできます。
graphqlが絡むから難しく感じると思う。これさえ理解できれば後はなんでも応用ができるはずです。
質問があれば、下に記入ください。

関連記事

コメント

コメントを書く

質問や、間違いがありましたら、お気軽にどうぞ

※お名前とコメントの内容を入力してください。
※全てこのブログ上に公表されます。
※許可なく管理者によって修正・削除する場合がございます。 詳しくはプライバシーポリシーを参照ください