dnrsm.dev

  • Archives
  • About
  • Works
  • GitHub

Gatsbyでブログを作りました

2020-05-25

Table of Contents

モチベーション

  • Qiitaに記事を書いていたのだが、より個人的なアウトプットの場として自前のブログが欲しくなった
  • 業務でReactを使う機会があまりないので触っておきたかった

技術スタック

Aboutにも書いてますが、技術的には以下の構成で作られています。

  • Gatsby
    • 静的サイトジェネレーター(SSG)の一つです。
  • TypeScript
    • DXが良いし入れない理由もないので入れる。
  • MDX
    • データソースはMDXを使用しています。Headless CMSより楽そうだし記事を手元で管理したかったから。
  • Netlify
    • ホスティング先。

もともとはstarterとして配布されているテーマをいじって作っていたのですが、TypeScriptに書き直したりデザインをいじっているうちに、ゼロから作ってしまった方が速いなと気付き今回のブログ構築に至ります。

ちなみに、その間にNext.jsで作ろうとしていた時期もあったのですが、Gatsbyの方がエコシステムが整っていたのでGatsbyで構築しました。

ソースコードを公開しています。 dnrsm/blog

やったこと

  • TypeScriptの導入
  • gatsby-plugin-graphql-codegenでGraphQLのスキーマから生成した型を使う
  • gatsby-*.js系のファイルをTypescriptで記述できるようにする
  • MDXで記事が書けるようにする
  • ESLint、Prettierの導入

TypeScriptの導入

gatsby-plugin-typescriptを入れるだけで書けるようになります。

$ yarn add -D typescript gatsby-plugin-typescript

gatsby-config.jsにプラグインとして追加します。

gatsby-config.js
module.exports = {
  ...
  plugins: [
    ...
    `gatsby-plugin-typescript`,
  ]
}

tsconfig.jsonを追加します。

tsconfig.json
{
    "compilerOptions": {
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "esnext",
        "jsx": "react",
        "lib": ["dom", "es2015", "es2017"],
        "esModuleInterop": true
    },
    "exclude": [  
        "node_modules"  
    ],
    "include": [
        "./src/**/*"
    ]
}

GraphQLのスキーマから生成した型を使う

gatsby-plugin-graphql-codegenを利用してGraphQLのスキーマから型を生成します。
このプラグインを知るまでは自分で型を書いてましたが、自動で生成されるようになってからはすごく楽になりました。
導入に関しては以下の記事を参照した。
Gatsby.js を完全TypeScript化する

$ yarn add gatsby-plugin-graphql-codegen
gatsby-config.js
module.exports = {
  ...
  plugins: [
    ...
    {
      resolve: "gatsby-plugin-graphql-codegen",
      options: {
        fileName: `types/graphql-types.d.ts`,
        documentPaths: [
          "./src/**/*.{ts,tsx}",
          "./node_modules/gatsby-*/**/*.js",
        ],
      },
    },
  ],
};

クエリの名前をBlogPostとすると、BlogPostQueryという名前で型が生成されます。

blog-post.tsx
import * as React from "react";
import { graphql, PageProps } from "gatsby";
import SEO from "../components/Seo";
import Layout from "../components/Layout";
import Post from "../components/Post";
import { BlogPostQuery, SitePageContext } from "../../types/graphql-types";

export type Props = PageProps<BlogPostQuery, SitePageContext>;

const BlogPostTemplate: React.FC<Props> = ({ data, pageContext }) => {
  const { body, frontmatter } = data.mdx;

  return (
    <Layout pageType={"post"}>
      <SEO title={frontmatter.title} description={frontmatter.description} />
      <Post body={body} frontmatter={frontmatter} pageContext={pageContext} />
    </Layout>
  );
};

export default BlogPostTemplate;

export const pageQuery = graphql`
  query BlogPost($path: String) {
    mdx(frontmatter: { path: { eq: $path } }) {
      body
      frontmatter {
        title
        date
        tags
        path
        description
      }
    }
  }
`;
types/graphql-types.d.ts
export type BlogPostQuery = {
  mdx?: Maybe<
    Pick<Mdx, "body"> & {
      frontmatter?: Maybe<
        Pick<MdxFrontmatter, "title" | "date" | "tags" | "path" | "description">
      >;
    }
  >;
};

gatsby-*.js系のファイルをTypeScriptで記述できるようにする

gatsby-*.js系のファイルもTypeScriptで記述できるようにしました。
ts-nodeを利用して対応させます。

$ yarn add -D ts-node
gatsby-node.js
"use strict";

require("ts-node").register(
  require("jsonc-parser").parse(
    require("fs").readFileSync("./tsconfig.json", "utf-8")
  )
);

module.exports = require("./src/gatsby-node");

実質的にエントリーポイントはsrc/gatsby-node/index.tsとなります。

src/gatsby-node/index.ts
import path from "path";
const blogPostTemplate = path.resolve(`./src/templates/blog-post.tsx`);
import { GatsbyNode } from "gatsby";
import {
  SiteSiteMetadata,
  MdxConnection,
  SitePageContext,
} from "../../types/graphql-types";

type Result = {
  allMdx: MdxConnection;
  site: {
    siteMetadata: SiteSiteMetadata;
  };
};

export const createPages: GatsbyNode["createPages"] = async ({
  graphql,
  actions: { createPage },
}) => {
  const query = `
    {
      allMdx(sort: { order: DESC, fields: frontmatter___date }, limit: 1000) {
        edges {
          node {
            frontmatter {
              path
              title
            }
          }
        }
        group(field: frontmatter___tags) {
          fieldValue
        }
      }
      site {
        siteMetadata {
          postsPerPage
        }
      }
    }
  `;

  const result = await graphql<Result>(query);

  if (result.errors) {
    throw result.errors;
  }

  const posts = result.data.allMdx.edges;
  const {
    site: { siteMetadata },
  } = result.data;

  posts.forEach((post, index) => {
    const previous = index === posts.length - 1 ? null : posts[index + 1].node;
    const next = index === 0 ? null : posts[index - 1].node;

    createPage<SitePageContext>({
      path: post.node.frontmatter.path,
      component: blogPostTemplate,
      context: {
        next,
        previous,
      },
    });
  });
};

ESLint、Prettierの導入

DX的に良い状態にしておきたいので入れておく。

$ yarn add -D eslint-config-prettier eslint-plugin-prettier

設定ファイルを書きます。

.eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:import/errors",
    "plugin:import/warnings",
    "plugin:import/typescript",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
    "prettier/react"
  ],
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "rules": {
    "react/prop-types": "off"
  },
  "overrides": [
    {
      "files": ["*.js"],
      "rules": {
        "@typescript-eslint/explicit-function-return-type": "off"
      }
    }
  ]
}

npm-scriptsに追加します。

package.json
"lint": "eslint --ignore-path .gitignore . --ext ts --ext tsx --ext js --ext tsx"

MDXで記事が書けるようにする

MDXとは、Markdownが拡張されたもので、文書の中にJSXを埋め込めるようにしたものです。
Markdownの中にコンポーネントを書けたりする。

$ yarn add gatsby-source-filesystem gatsby-plugin-mdx gatsby-remark-images gatsby-remark-code-titles gatsby-remark-prismjs

gatsby-source-filesystemgatsby-plugin-mdxだけでMDXからページの作成はできますが、シンタックスハイライターやらなんやらを使いたいので、gatsbyRemarkPluginsの中にgatsby-remark-*系のプラグインを設定してます。

gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `post`,
        path: `${__dirname}/src/post`,
      },
    },
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        extensions: [`.mdx`, `.md`],
        defaultLayouts: {
          default: require.resolve("./src/components/Layout.tsx"),
        },
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 800,
              quality: 100,
            },
          },
          `gatsby-remark-code-titles`,
          {
            resolve: `gatsby-remark-prismjs`,
            options: {
              classPrefix: "language-",
              inlineCodeMarker: true,
              aliases: {},
              showLineNumbers: true,
              noInlineHighlight: false,
            },
          },
        ],
      },
    },
    ...
  ],
};

まとめ

とりあえず記事書くことろまではできた。

今後は以下の対応も進めていきたい。そのうちやる。

  • ダークモードの追加
  • PWA化
Vue + Composition API + TypeScript + WebpackなプロジェクトでStorybookを構築する
© 2020 dnrsm.dev