Gatsbyを使ってみる

# Gatsbyとは

Reactを使った静的サイト生成ツール
static PWA(Progressive Web Application)を作れるらしい。PWAとはネイティブアプリのようなWEBアプリと自分は解釈している。

Reactのことをさっぱり分かっていないこともあり、学習も兼ねて使ってみた。
静的サイト生成ツールは[こちら](https://www.staticgen.com/)の一覧が参考になった。

# markdownを書いてblog記事にするためのhow to

## 1. インストールと設定
gatsby-plugin-react-helmetが2系だと、`$ gatsby develop`でエラー出てたけどもう直ってるかも？

```
$ node -v 
v9.3.0
$ npm install -g gatsby-cli
$ gatsby new my-blog
$ cd my-blog
$ npm install gatsby-source-filesystem gatsby-transformer-remark
$ npm ls -g --depth=0
/home/kw/.nvm/versions/node/v9.3.0/lib
├── gatsby-cli@1.1.28
└── npm@5.5.1
$ npm ls --depth=0
gatsby-starter-default@1.0.0 /path/to/my-blog
├── gatsby@1.9.145
├── gatsby-link@1.6.32
├── gatsby-plugin-react-helmet@1.0.8
├── gatsby-source-filesystem@1.5.11
├── gatsby-transformer-remark@1.7.26
└── prettier@1.9.2
```

gatsby-config.jsを下記のように編集

```
module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src`,
      },
    },
  ],
};
```
## 2. markdownでブログ記事を書く

```
$ cd src/pages
$ mkdir -p 2018/01/14
$ touch 2018/01/14/hello-my-blog.md
```

hello-my-blog.mdは次のように記述する


```

---
path: "/2018/01/14/hello-my-blog"
date: "2018-01-14T22:35:00Z"
title: "Gatsbyとgithub pagesでブログ"
tags: ['gatsby', 'react']
excerpt: "Gatsbyとgithub pagesでブログを作成してみた"
---

# Gatsbyとは
.
.
.
```

## 3. index.jsを修正する

src/pages/index.jsを修正する

```
import React from 'react';
import Link from 'gatsby-link';

const IndexPage = ({data}) => {
  const {edges: posts} = data.allMarkdownRemark;
  return (
      <div>
        {posts.map(({node: post}) => {
          const {frontmatter} = post;
          return (
              <div key={post.id}>
                <h2>
                  <Link to={frontmatter.path}>
                    {frontmatter.title}
                  </Link>
                </h2>
                <p>{frontmatter.date}</p>
                <p>{frontmatter.excerpt}</p>
              </div>
          );
        })}
      </div>
  );
};

export const query = graphql`
  query IndexQuery {
    allMarkdownRemark {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "MMMM DD, YYYY")
            path
            tags
            excerpt
          }
        }
      }
    }
  }
`;

export default IndexPage;
```

localサーバーを起動して確かめる

localhost:8000にアクセスできる

```
$ gatsby develop
```

## 4. Blogコンテンツ内容を表示するテンプレートの作成

blog記事の一覧をindexで表示することはできたが、個別のページへのリンクをクリックすると404のままなので、テンプレートを作成する。

```
$ pwd
src
$ ls
layouts pages
$ mkdir templates
$ touch templates/blog-post.js
```

blog-post.jsを編集して次のようにする

```
import React from 'react';
import Link from 'gatsby-link';
import Helmet from 'react-helmet';

const Template = ({data, location, pathContext}) => {
  const {markdownRemark: post} = data;
  const {frontmatter, html} = post;
  const {title, date} = frontmatter;

  return (
      <div>
        <Helmet title={`${frontmatter.title} - My Blog`}/>
        <div>
          <h1>{title}</h1>
          <h3>{date}</h3>
          <div dangerouslySetInnerHTML={{__html: html}}/>
        </div>
      </div>
  );
};

export const pageQuery = graphql`
  query BlogPostByPath($path: String!){
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        path
        tags
        excerpt
      }
    } 
  }
`;

export default Template;
```

## 5. Dynamicにコンテンツを生成する

gatsby-node.jsを編集して次のようにする。

```
/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */
const path = require('path');

exports.createPages = ({boundActionCreators, graphql}) => {
  const {createPage} = boundActionCreators;
  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);

  return graphql(`{
    allMarkdownRemark {
      edges {
        node {
          html
          id
          frontmatter {
            date
            path
            title
            excerpt
            tags
          }
        }
      }
    }
  }`).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }
    const posts = result.data.allMarkdownRemark.edges;

    posts.forEach(({node}, index) => {
      createPage({
        path: node.frontmatter.path,
        component: blogPostTemplate,
      });
    });
  });
};
```

これでblog記事へのリンクをクリックするとコンテンツが表示される

```
$ gatsby develop
```

## 6. 前後の記事へのリンクをつくる

blog-post.jsは下記のように修正

```
import React from 'react';
import Link from 'gatsby-link';
import Helmet from 'react-helmet';

const Template = ({data, location, pathContext}) => {
  const {markdownRemark: post} = data;
  const {frontmatter, html} = post;
  const {title, date} = frontmatter;
  const {next, prev} = pathContext;

  return (
      <div>
        <Helmet title={`${frontmatter.title} - My Blog`}/>
        <div>
          <h1>{title}</h1>
          <h3>{date}</h3>
          <div dangerouslySetInnerHTML={{__html: html}}/>
          <p>
            {prev && (
                <Link to={prev.frontmatter.path}>
                  Previous: {prev.frontmatter.title}
                </Link>
            )}
          </p>
          <p>
            {next && (
                <Link to={next.frontmatter.path}>
                  Next: {next.frontmatter.title}
                </Link>
            )}
          </p>
        </div>
      </div>
  );
};

export const pageQuery = graphql`
  query BlogPostByPath($path: String!){
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        path
        tags
        excerpt
      }
    } 
  }
`;

export default Template;
```

gatsby-node.jsは次のように修正

```
/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */
const path = require('path');

exports.createPages = ({boundActionCreators, graphql}) => {
  const {createPage} = boundActionCreators;
  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);

  return graphql(`{
    allMarkdownRemark {
      edges {
        node {
          html
          id
          frontmatter {
            date
            path
            title
            excerpt
            tags
          }
        }
      }
    }
  }`).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }
    const posts = result.data.allMarkdownRemark.edges;

    posts.forEach(({node}, index) => {
      createPage({
        path: node.frontmatter.path,
        component: blogPostTemplate,
        context: {
          prev: index === 0 ? null : posts[index - 1].node,
          next: index === (posts.length - 1) ? null : posts[index + 1].node,
        },
      });
    });
  });
};
```


## 7. tag一覧を作る

テンプレートを作成する

```
$ pwd
blog/src
$ ls
layouts pages templates
$ touch templates/all-tags.js
$ touch templates/tags.js
```

all-tags.jsは下記のように

```
import React from 'react';
import Link from 'gatsby-link';

const AllTags = ({pathContext}) => {
  const {tags} = pathContext;

  if (tags) {
    return (
        <div>
          <ul>
            {tags.map(tag => {
              return (
                  <li>
                    <Link to={`/tags/${tag}`}>
                      {tag}
                    </Link>
                  </li>
              );
            })}
          </ul>
        </div>
    );
  }
};

export default AllTags;
```

tags.jsは次のように

```
import React from 'react';
import Link from 'gatsby-link';

const Tags = ({pathContext}) => {
  const {posts, tagName} = pathContext;

  if (posts) {
    return (
        <div>
          <span>
            Posts abount {tagName}:
          </span>
          <ul>
            {posts.map(post => {
              return (
                  <li>
                    <Link to={post.frontmatter.path}>
                      {post.frontmatter.title}
                    </Link>
                  </li>
              );
            })}
          </ul>
        </div>
    );
  }
};

export default Tags;
```

gatsby-node.jsは次のようにtagに関する記述を追加する

```
/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */
const path = require('path');

const createTagPages = (createPage, posts) => {
  const tagPageTemplate = path.resolve(`src/templates/tags.js`);
  const allTagsTemplate = path.resolve(`src/templates/all-tags.js`);

  const postsByTags = {};

  posts.forEach(({node}) => {
    if (node.frontmatter.tags) {
      node.frontmatter.tags.forEach(tag => {
        if (!postsByTags[tag]) {
          postsByTags[tag] = [];
        }
        postsByTags[tag].push(node);
      });
    }
  });

  const tags = Object.keys(postsByTags);

  createPage({
    path: `/tags`,
    component: allTagsTemplate,
    context: {
      tags: tags.sort(),
    },
  });

  tags.forEach(tagName => {
    const posts = postsByTags[tagName];

    createPage({
      path: `/tags/${tagName}`,
      component: tagPageTemplate,
      context: {
        posts,
        tagName,
      },
    });
  });
};

exports.createPages = ({boundActionCreators, graphql}) => {
  const {createPage} = boundActionCreators;
  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);

  return graphql(`{
    allMarkdownRemark {
      edges {
        node {
          html
          id
          frontmatter {
            date
            path
            title
            excerpt
            tags
          }
        }
      }
    }
  }`).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }
    const posts = result.data.allMarkdownRemark.edges;

    createTagPages(createPage, posts);

    posts.forEach(({node}, index) => {
      createPage({
        path: node.frontmatter.path,
        component: blogPostTemplate,
        context: {
          prev: index === 0 ? null : posts[index - 1].node,
          next: index === (posts.length - 1) ? null : posts[index + 1].node,
        },
      });
    });
  });
};
```

さらにpages/index.jsを修正

```
import React from 'react';
import Link from 'gatsby-link';

const IndexPage = ({data}) => {
  const {edges: posts} = data.allMarkdownRemark;
  return (
      <div>
        {posts.map(({node: post}) => {
          const {frontmatter} = post;
          return (
              <div key={post.id}>
                <h2>
                  <Link to={frontmatter.path}>
                    {frontmatter.title}
                  </Link>
                </h2>
                <p>{frontmatter.date}</p>
                <p>{frontmatter.excerpt}</p>
                <ul key={post.id}>
                  {post.frontmatter.tags.map(tag => {
                    return (
                        <li>
                          <Link to={`/tags/${tag}`}>
                            {tag}
                          </Link>
                        </li>
                    );
                  })}
                </ul>
              </div>
          );
        })}
      </div>
  );
};

export const query = graphql`
  query IndexQuery {
    allMarkdownRemark {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "MMMM DD, YYYY")
            path
            tags
            excerpt
          }
        }
      }
    }
  }
`;

export default IndexPage;
```

## 8. github pagesにdeployする

モジュールを追加する

```
$ npm install --save gh-pages
```

package.jsonを修正する

```
.
.
"dependencies": {
    "gatsby": "^1.9.145",
    "gatsby-link": "^1.6.32",
    "gatsby-plugin-react-helmet": "^1.0.8",
    "gatsby-source-filesystem": "^1.5.11",
    "gatsby-transformer-remark": "^1.7.26",
    "gh-pages": "^1.1.0"
  },
  "keywords": [
    "gatsby"
  ],
  "license": "MIT",
  "main": "n/a",
  "scripts": {
    "build": "gatsby build",
    "deploy": "gatsby build --prefix-paths && gh-pages -d public",
    "develop": "gatsby develop",
    "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"src/**/*.js\"",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
.
.
```

deployコマンドが追加されている。

github pagesを使ってサイトを作るにはレポジトリを作成して設定からgithub pagesの設定をすることで作成可能になる。

プロジェクト名がパス名になるので、このプロジェクトの名前はblogだ。(private repoにしているがgithub pagesは利用可能だ)

デプロイするには下記のコマンドを実行する

```
$ npm run-script deploy
```

https://abcb2.github.io/blog

でアクセスできるようになっている。