プロジェクトディレクトリ内のマークダウンを 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 ディレクトリを作成し、ここにマークダウンファイルを配置するようにします。
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `data`,
path: `${__dirname}/data/`,
},
},
`gatsby-transformer-remark`,
],
}
GraphiQL(http://localhost:8000/\_\_\_graphq) にアクセスし、allMarkdownRemark と markdownRemark が表示されていることを確認します。
マークダウンファイルの作成
data ディレクトリに file1.md、file2.md を作成します。 ファイルの最初に「-」に囲まれたスペースがあります。これは「frontmatter」と呼ばれ GraphQL から参照できます。
---
path: "/file1"
date: "2019-06-16"
title: "マークダウンファイル その1"
---
## マークダウンファイルの内容
---
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 ディレクトリ直下でなくても検索できるので、日付ディレクトリやカテゴリディレクトリを作成して、その配下にマークダウンを作成しても表示できます。
GraphQL で取得した内容を画面に表示する
index.js にマークダウンの内容を一覧で表示します。マークダウンファイルの件数と frontmatter(date、title)、抜粋を表示します。
emotion で CSS を記述するのでインストールします。
npm install --save gatsby-plugin-emotion @emotion/core @emotion/styled
// 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-node.js の処理で マークダウンファイルの内容を埋め込む感じとなります。
テンプレートの作成
マークダウンの内容を表示するテンプレートを「src/templates/blog.js」に作成します。 内容は GraphQL で title と html を取得し表示するページとなりますが、どのマークダウンか特定するために fields の slug が「$slug」と一致するという条件を指定します($slug は gatsby-node.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 の内容が出力されます。
今回は、マークダウンの 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-node.js でページの生成
次に、ページの生成をします。 gatsby-node.js に onCreateNode を追加します、onCreateNode はノードの作成が完了した後に実行されます。
onCreateNode では以下の内容実装します。
- テンプレートの絶対パスを取得
- path.resolve で絶対パスの取得ができます(事前に require(
path
)で path を読み込んでおく)。
- path.resolve で絶対パスの取得ができます(事前に require(
- マークダウンの 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 にアクセスすると マークダウンの内容が確認できます。
一覧にリンクを設定
index.js の一覧表示から各ページへ遷移できるようにします。
- GraphQL で fields.slug を取得
- Link コンポーネントでリンクを作成し、遷移先に fields.slug を指定する
// 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
}
}
}
}
}
`