bibi

学んだ技術や本をアウトプットをするだけ

Gatsbyチュートリアル

静的サイトジェネレータとして有名なGatsby

触ったことが無かったが、ちょうど使用する機会があったため、公式チュートリアルを一通りやってみる。
その雑多なまとめ。

今回触れていること

公式チュートリアルはこちら www.gatsbyjs.com

とても親切に出来ていて助かる。

Gatsbyとは

この記事が分かりやすい。 qiita.com

ここも見とくと具体的に何が利点なのか分かる。 qiita.com

特徴としては以下が挙げられる。

  • Reactで作られた静的サイトジェネレーター
  • 静的サイトジェネレーターだが、axiosなどを使った、非同期のdata fetchも可能。
  • GraphQLと相性良い
  • Gatsbyプラグインが多数あり便利(TypeScript化,PWA対応...)
  • 爆速
  • サーバーを気にしないで済む

Reactを触ったことがある人ならすぐ使える。
学習コストがあまり高くないのは利点。
GraphQLは完全に把握するのは少し難易度高そうな感じ。

仕組みを大まかに把握して所で実際に触ってみる。

0. Set Up Your Development Environment

環境構築はチュートリアルに沿っていけば問題ない。

# gatsbyサイト新規作成
gatsby new [SITE_DIRECTORY_NAME] [URL_OF_STARTER_GITHUB_REPO]
# 開発サーバー起動(http://localhost:8000/ で確認)
gatsby develop

 1. Get to Know Gatsby Building Blocks(Surgeを使用したデプロイ)

JSXの説明、Reactの説明がされているがこの部分はさっと読み流し。

「src/pages/」に設置するとページとして認識される。
試しに「src/pages/about.js」を作成し、「http://localhost:8000/about」で確認してみるとページが確認できる。

再利用できるコンポーネントはサブコンポーネントとして「src/components」に作る。
「src/components/header.js」を作成し、about.jsで読み込んでみるとheader.jsが/aboutに表示されていることが確認できる。

Gatsbyコンポーネント(Link)の使用は以下の様に行う。

import React from "react"
import { Link } from "gatsby"
import Header from "../components/header"
export default function Home() {
  return (
    <div style={{ color: `purple` }}>
      <Link to="/contact/">Contact</Link>
      <Header headerText="Hello Gatsby!" />
      <p>What a world.</p>
      <img src="https://source.unsplash.com/random/400x200" alt="" />
    </div>
  )
}

Gatsbyサイトで処理されないページへの外部リンクについては、通常のaタグで行うとのこと。

GatsbyというかReactのチュートリアルに近い。

Surgeを使用したデプロイが紹介されていたので試す。

npm install --global surge
# surgeアカウントがない場合は画面に従って作成
surge login

# ビルドするとpublicフォルダに生成される
gatsby build

# surgeデプロイ
surge public/
# domainに記載されているURLを開くとページが確認できる

surgeについてはこちら surge.sh

コマンド解説 dotstud.io

2. Introduction to Styling in Gatsby

グローバルスタイルを試す。

gatsby-browser.jsをルートディレクトリに作成し、任意の場所に作成したCSSファイルを読み込むと、全体のページにスタイルが適用されているのが確認できる。

gatsby-browser.js

import "./src/styles/global.css"

CSSモジュールを試す。

以下のディレクトリとファイルを作成。

src/components/container.js

import React from "react"
import containerStyles from "./container.module.css"
export default function Container({ children }) {
  return <div className={containerStyles.container}>{children}</div>
}

src/components/container.module.css

.container {
  margin: 3rem auto;
  max-width: 600px;
}

src/pages/about-css-modules.js

import React from "react"
import Container from "../components/container"
export default function About() {
  return (
    <Container>
      <h1>About CSS Modules</h1>
      <p>CSS Modules are cool</p>
    </Container>
  )
}

/about-css-modulesを確認するとCSSが適用されていることが確認できる。

コンポーネントを作成し、そのコンポーネントにスタイルを設定する場合は以下の様にする。

src/pages/about-css-modules.js

import React from "react"
import styles from "./about-css-modules.module.css"
import Container from "../components/container"

const User = props => (
  <div className={styles.user}>
    <img src={props.avatar} className={styles.avatar} alt="" />
    <div className={styles.description}>
      <h2 className={styles.username}>{props.username}</h2>
      <p className={styles.excerpt}>{props.excerpt}</p>
    </div>
  </div>
)

export default function About() {
  return (
    <Container>
      <h1>About CSS Modules</h1>
      <p>CSS Modules are cool</p>
      <User
        username="Jane Doe"
        avatar="https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg"
        excerpt="I'm Jane Doe. Lorem ipsum dolor sit amet, consectetur adipisicing elit."
      />
      <User
        username="Bob Smith"
        avatar="https://s3.amazonaws.com/uifaces/faces/twitter/vladarbatov/128.jpg"
        excerpt="I'm Bob Smith, a vertically aligned type of guy. Lorem ipsum dolor sit amet, consectetur adipisicing elit."
      />
    </Container>
  )
}

3. Creating Nested Layout Components

Typography.jsの使用を試す。

npm install gatsby-plugin-typography react-typography typography typography-theme-fairy-gates

ルートディレクトリにgatsby-config.jsを作成。

gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
  ],
}

gatsby-config.jsはプラグインやその他のサイト構成を追加するファイルとのこと。
詳細はここから。 www.gatsbyjs.com

Typography.jsの設定ファイルを作成

src/utils/typography.js

import Typography from "typography"
import fairyGateTheme from "typography-theme-fairy-gates"

const typography = new Typography(fairyGateTheme)

export const { scale, rhythm, options } = typography
export default typography

開発サーバーを再起動して、Chrome開発者ツールでheadタグ内を見てみると、
styleタグ内にTypography.jsのスタイルが記載されていることが確認できる。
また、ページ内のフォントが変更されていることが確認できる。

レイアウトコンポーネントについて。

以下の様に枠を作成して、呼び出すだけで統一したレイアウトにすることが出来る。

src/components/layout.js

import React from "react"

export default function Layout({ children }) {
  return (
    <div style={{ margin: `3rem auto`, maxWidth: 650, padding: `0 1rem` }}>
      {children}
    </div>
  )
}

src/pages/index.js

import React from "react"
import Layout from "../components/layout"

export default function Home() {
  return (
    <Layout>
      <h1>Hi! I'm building a fake Gatsby site as part of a tutorial!</h1>
      <p>
        What do I like to do? Lots of course but definitely enjoy building
        websites.
      </p>
    </Layout>
  );
}

layout.jsにナビゲーションリンクなど付けると更に便利だよ、というような内容だった。

4. Data in Gatsby(GraphQL)

GraphQLの公式サイトにもチュートリアルがある。 www.howtographql.com

まず、サイトのタイトルがバラバラなので1つの場所にまとめましょうとのこと。
gatsby-config.jsのsiteMetadata>titleに任意のタイトル名を記載。

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: `Title from siteMetadata`,
  },
  plugins: [
    `gatsby-plugin-emotion`,
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
  ],
}

以下の様にするとgatsby-config.jsで指定したtitleを使用できる。
開発サーバーを再起動すると確認できる。
gatsbyからgraphqlを読み込んで、const queryを作成している。
ページコンポーネントでのみ使用可能とのこと。

src/pages/about.js

import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default function About({ data }) {
  return (
    <Layout>
      <h1>About {data.site.siteMetadata.title}</h1>
      <p>
        We're the only site running on your computer dedicated to showing the
        best photos and videos of pandas eating lots of food.
      </p>
    </Layout>
  )
}

export const query = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
  }
`

StaticQueryの使用を試す。

StaticQueryはGatsbyのバージョン2で導入された新しいAPIで、ページ以外のコンポーネント(layout.jsコンポーネントなど)がGraphQLクエリを介してデータを取得できる、とのこと。

useStaticQueryを使用する。

src/components/layout.js

import React from "react"
import { css } from "@emotion/core"
import { useStaticQuery, Link, graphql } from "gatsby"

import { rhythm } from "../utils/typography"
export default function Layout({ children }) {
  const data = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
          }
        }
      }
    `
  )
  return (
    <div
      css={css`
        margin: 0 auto;
        max-width: 700px;
        padding: ${rhythm(2)};
        padding-top: ${rhythm(1.5)};
      `}
    >
      <Link to={`/`}>
        <h3
          css={css`
            margin-bottom: ${rhythm(2)};
            display: inline-block;
            font-style: normal;
          `}
        >
          {data.site.siteMetadata.title}
        </h3>
      </Link>
      <Link
        to={`/about/`}
        css={css`
          float: right;
        `}
      >
        About
      </Link>
      {children}
    </div>
  )
}

gatsby-config.jsのタイトルを変えてみるとすぐに反映することが確認できる。

5. Source Plugins

GraphQLとソースプラグインを使用してGatsbyサイトにデータを取り込む方法について試す。

名前がややこしいがGraphiQLというGraphQL統合開発環境があるらしい。 http://localhost:8000/___graphqlでアクセスできる。

使いたいデータ構造を予め作成できる感じか。

Gatsbyサイトのデータは、API、データベース、CMS、ローカルファイルなど、どこからでも取得できるとのこと。

例としてgatsby-source-filesystemを使用している。

npm install gatsby-source-filesystem

gatsby-config.jsにgatsby-source-filesystemを追加。

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: `Pandas Eating Lots`,
  },
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src/`,
      },
    },
    `gatsby-plugin-emotion`,
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
  ],
}

開発サーバーを再起動し、GraphiQLで確認すると、allFile、fileが追加されている。

GraphiQLでクエリにフィールドを追加して、実行すると結果が確認できる。
因みにExplolerボタンでコピペ用のコードが表示される。

試しに以下の様にして確認してみる。
(/pageで作成したファイル一覧が表示される)

src/pages/my-files.js

import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default function MyFiles({ data }) {
  console.log(data)
  return (
    <Layout>
      <div>
        <h1>My Site's Files</h1>
        <table>
          <thead>
            <tr>
              <th>relativePath</th>
              <th>prettySize</th>
              <th>extension</th>
              <th>birthTime</th>
            </tr>
          </thead>
          <tbody>
            {data.allFile.edges.map(({ node }, index) => (
              <tr key={index}>
                <td>{node.relativePath}</td>
                <td>{node.prettySize}</td>
                <td>{node.extension}</td>
                <td>{node.birthTime}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </Layout>
  )
}

export const query = graphql`
  query {
    allFile {
      edges {
        node {
          relativePath
          prettySize
          extension
          birthTime(fromNow: true)
        }
      }
    }
  }
`

6. Transformer plugins

ファイル内のデータをクエリすることを可能にするためにTransformer pluginsがあるとのこと。

例としてマークダウンファイルをHTMLに変換するgatsby-transformer-remarkを使用している。
ここでやっていることは、マークダウンファイルを読み込んでトップページにリストとして表示、という感じ。

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

src/pages/sweet-pandas-eating-sweets.md

---
title: "Sweet Pandas Eating Sweets"
date: "2017-08-10"
---

Pandas are really sweet.

Here's a video of a panda eating sweets.

<iframe width="560" height="315" src="https://www.youtube.com/embed/4n0xNbfJLR8" frameborder="0" allowfullscreen></iframe>
npm install gatsby-transformer-remark

gatsby-config.js修正

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: `Pandas Eating Lots`,
  },
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src/`,
      },
    },
    `gatsby-transformer-remark`,
    `gatsby-plugin-emotion`,
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
  ],
}

開発サーバーを再起動すると、allMarkdownRemarkが追加されているので、マークダウンファイルに関するフィールドを使用することができる。

index.jsを修正して試す。

src/pages/index.js

import React from "react"
import { graphql } from "gatsby"
import { css } from "@emotion/core"
import { rhythm } from "../utils/typography"
import Layout from "../components/layout"

export default function Home({ data }) {
  console.log(data)
  return (
    <Layout>
      <div>
        <h1
          css={css`
            display: inline-block;
            border-bottom: 1px solid;
          `}
        >
          Amazing Pandas Eating Things
        </h1>
        <h4>{data.allMarkdownRemark.totalCount} Posts</h4>
        {data.allMarkdownRemark.edges.map(({ node }) => (
          <div key={node.id}>
            <h3
              css={css`
                margin-bottom: ${rhythm(1 / 4)};
              `}
            >
              {node.frontmatter.title}{" "}
              <span
                css={css`
                  color: #bbb;
                `}
              >
                — {node.frontmatter.date}
              </span>
            </h3>
            <p>{node.excerpt}</p>
          </div>
        ))}
      </div>
    </Layout>
  )
}

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

新たなマークダウンファイルを作成すると、/indexの表示が変わることが確認できる。

またソートも可能で、GraphiQLでallMarkdownRemarkのソートフィールドを追加すれば、ソートが可能になる。
ここで便利さを改めて実感した。

7. Programmatically create pages from data

今度はマークダウンファイルごとのページを作成する。

そのためにはslugの作成が必要とのこと(https://www.gatsbyjs.com/tutorial/part-seven/の/tutorial/part-sevenの部分をslugと言う)。

Gatsby APIの内、2つのAPI(onCreateNode,createPages)を使用してマークダウンファイルからページを作成する。

Gatsby APIを使用するにはgatsby-node.jsを作成し、APIの名前を付けて関数をエクスポートする。

gatsby-node.js

exports.onCreateNode = ({ node }) => {
  console.log(`Node created of type "${node.internal.type}"`)
}

開発サーバーを再起動すると、ターミナルにコンソールログが表示される。

進んでいくと、以下の様なファイル名を取得する処理の話になる。
MarkdownRemarkノードではファイル名が取得できないため、
getNode(node.parent)で親ノードを指定し、そこからrelativePathを取得しているとのこと。

gatsby-node.js

exports.onCreateNode = ({ node, getNode }) => {
  if (node.internal.type === `MarkdownRemark`) {
    const fileNode = getNode(node.parent)
    console.log(`\n`, fileNode.relativePath)
  }
}

ただ、gatsby-source-filesystemで既に機能としてあるため以下の様な書き方で、slugの作成まで行うことが可能。

gatsby-node.js

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode }) => {
  if (node.internal.type === `MarkdownRemark`) {
    console.log(createFilePath({ node, getNode, basePath: `pages` }))
  }
}

次に、nodeの作成(API→onCreateNode)。

gatsby-node.js

const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

GraphiQLで確認すると、新たなslugが追加されている。

次に、ページ作成(API→createPages)。

gatsby-node.js

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `)

  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.fields.slug,
      component: path.resolve(`./src/templates/blog-post.js`),
      context: {
        // Data passed to context is available
        // in page queries as GraphQL variables.
        slug: node.fields.slug,
      },
    })
  })
}

テンプレートを通してページとなるため、専用のファイルを作成。

src/templates/blog-post.js

import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default function BlogPost({ data }) {
  const post = data.markdownRemark
  return (
    <Layout>
      <div>
        <h1>{post.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

開発サーバーを再起動し、マークダウンファイルの名前でアクセスするか、
適当にhttp://localhost:8000/sdfと存在しないパスを指定してから、対象のページへリンクすると確認できる。

最後にトップページにリンクを設定。

src/pages/index.js

import React from "react"
import { css } from "@emotion/core"
import { Link, graphql } from "gatsby"
import { rhythm } from "../utils/typography"
import Layout from "../components/layout"

export default function Home({ data }) {
  return (
    <Layout>
      <div>
        <h1
          css={css`
            display: inline-block;
            border-bottom: 1px solid;
          `}
        >
          Amazing Pandas Eating Things
        </h1>
        <h4>{data.allMarkdownRemark.totalCount} Posts</h4>
        {data.allMarkdownRemark.edges.map(({ node }) => (
          <div key={node.id}>
            <Link
              to={node.fields.slug}
              css={css`
                text-decoration: none;
                color: inherit;
              `}
            >
              <h3
                css={css`
                  margin-bottom: ${rhythm(1 / 4)};
                `}
              >
                {node.frontmatter.title}{" "}
                <span
                  css={css`
                    color: #555;
                  `}
                >
                  — {node.frontmatter.date}
                </span>
              </h3>
              <p>{node.excerpt}</p>
            </Link>
          </div>
        ))}
      </div>
    </Layout>
  )
}

export const query = graphql`
  query {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`

8. Preparing a Site to Go Live

Lighthouseでサイト診断。

# ビルド
gatsby build
# ローカルで本番サイトを確認するためのコマンド
gatsby serve

こんな感じで診断結果が表示される。 f:id:dollsSK:20200925224118p:plain

ここでチュートリアルは大体完了。

9.GAEへデプロイ

GCPは完全に初心者だったので、まず環境構築からだったが、デプロイ完了までそこまで時間はかからなかった(1,2時間くらい?)。

以下の記事を元に、まずデプロイするまでざっくりやってみる。

公式サイトとこちらの記事を参考。 cloud.google.com shotat.hateblo.jp

gcloudコマンドのインストール等も公式とこちらの記事を参考にした。 qiita.com

初期設定等が完了したら、デプロイするアプリのルートディレクトリにapp.yamlを作成。

runtime: php55
api_version: 1

handlers:
# file with extensions (e.g. .html)
- url: /(.*\..*)
  static_files: public/\1
  upload: public/(.*)

- url: /(.*)/
  static_files: public/\1/index.html
  upload: public/(.*)/index.html

- url: /
  static_files: public/index.html
  upload: public/index.html

- url: /(.*)
  static_files: public/\1/index.html
  upload: public/(.*)/index.html

skip_files:
  - node_modules/
  - src/
  - \.cache/
  - package\.json
  - yarn\.lock

後はコマンドを打つだけ。

# デプロイ
gcloud app deploy
# アプリの表示
gcloud app browse

まとめ

次は簡単なwebアプリを作ってみる。
GraphQL、GCP(GAE)も今回初めて触ったので、まとめて記事にする。
Next.jsもあまり明るくないので、実際に触ってみてGatsbyと比較してみる。