notebook

都内でWEB系エンジニアやってます。

Gatsbyで記事用のMarkdownとサンプル用のMarkdownを分けて扱う

Gatsby製のブログで、コードハイライトにはプラグインとしてgatsby-remark-prismjsを使っている

特に設定などせず使っていたが、調べたら行番号指定でのハイライトや、シェル表示など色々なものがあるようだったのでローカルで試していた

しかし、prism-coyのテーマが良くないのか組み合わせが悪いのか、結構意図と反する表示になることが多く、CSSをがっつり修正することにした

その際に、このケースが載っている記事はあったっけな…というのを毎度探すのがたいへんだったので、どうせならある程度のユースケースを網羅しているサンプル用の記事を用意してそこを見ればOKというような状態にしたい

あわよくばVisualRegressionTestまで行えれば良さそうではということでサンプル用の記事を作ることにした

やりたいことは下記

  • Markdownでサンプル用の記事を作成したい、既存の記事と同様
  • 記事一覧やArchiveには載らないようにしたい

調べたらsource filesystemで複数ソースを扱うことができるよう

参考

Gatsbyで2種類のマークダウンファイルの区別する方法Takumon Blog

takumon.com

まさにやろうとしているのはこれだった、以降で自身でも実際にやったことを残していく

ディレクトリ分割してsource filesystemで読み込む

通常の記事をblog、今回作成するサンプル用の記事をsampleとして扱う

ディレクトリ

  • blog
    • content/blog/entries/以下にMarkdownファイル
  • sample
    • content/sample/samples/以下にMarkdownファイル

sourceの読み込み

  • gatsby-config.ts

plugins以下の設定にsample用の設定を加える

  {
    resolve: `gatsby-source-filesystem`,
    options: {
      path: `${__dirname}/content/blog`,
      name: `blog`,
    },
  },
+  {
+    resolve: `gatsby-source-filesystem`,
+    options: {
+      path: `${__dirname}/content/sample`,
+      name: `sample`,
+    },
+  },

以降nameを元に区別をしていく

GatsbyのNodeにフィールド追加する

Gatsbyではビルドプロセス中に、NodeといってMarkdownファイル、画像ファイル、ファイルなどのデータの単位を生成している

そのNodeに対してGraphQLでクエリを投げて取得したデータを元に色々な処理や表示をさせることができるという仕組みになっている

各種プラグインではこのNodeに対して特定のソースではこのNodeを生成するという処理をしている

そして、Nodeの生成時に呼び出されるAPIとしてonCreateNodeという物があり、生成されるNodeに対して独自の処理を加えることができる

よくあるのはフィールド追加などかな

今回はMarkdownRemarkのNodeにcollectionというフィールドを追加した

  • gatsby-node.ts(一部抜粋)
export const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

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

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

    const parent = getNode(node.parent)

    createNodeField({
      name: "collection",
      node,
      value: parent?.sourceInstanceName,
    })
  }
}

MarkdownRemarkのparentはFileのNodeでFileにはsourceInstanceNameというフィールドがあり、そこに前節で設定したblogsampleなどが設定されている

GraphQLでFileに対してクエリして確認した

  • Fileへのクエリ
query File {
  allFile {
    nodes {
      sourceInstanceName
      name
    }
  }
}
  • クエリ結果一部抜粋
{
  "data": {
    "allFile": {
      "nodes": [
        {
          "sourceInstanceName": "blog",
          "name": "algolia_mock_with_msw"
        },
        {
          "sourceInstanceName": "sample",
          "name": "prismjs"
        }
      ]
    }
  }
}

collectionというフィールド名でその値を設定する

これでMarkdownRemarkのNodeでblogsampleの区別ができるようになる

フィールド追加後、MarkdownRemarkにクエリした結果の一部抜粋

query AllMarkdown {
  allMarkdownRemark {
    edges {
      node {
        fields {
          slug
          collection
        }
      }
    }
  }
}
{
  "node": {
    "fields": {
      "slug": "/entries/vscode_terminal_profile/",
      "collection": "blog"
    }
  }
},
{
  "node": {
    "fields": {
      "slug": "/samples/prismjs/",
      "collection": "sample"
    }
  }
},

OKそう

GraphQLクエリでフィルタリングする

前節でMarkdownRemarkのNodeにcollectionフィールドを追加した

今までは想定として単一種のMarkdownソースを扱うようにGraphQLのクエリを書いていたため、ブログ記事を想定している各画面ではブログ記事orサンプルを区別する必要がある

ということで、すべてのGraphQLクエリに変更が必要…

といっても今回のケースはそんなに多くなかったのでひとつずつ書き換えた

allMarkdownRemark(sort: { frontmatter: { date: DESC } }, filter: { fields: { collection: { eq: "blog" } } }) {
  edges {
    node {
.....
.....
.....

MarkdownRemarkに対してfilterfields: { collection: {eq: "blog" } }というように取得したいcollectionを指定する

実際に一覧ではクエリで絞ってサンプル記事を表示しないようにした

まとめ

Gatsbyのブログサイトで、通常の記事とサンプル記事を区別して扱えるようにした

  • gatsby-source-filesystemで複数のMarkdownソースを管理
  • GatsbyのNodeにフィールドを追加して後続プロセスで区別できるような値を設定
  • GraphQLクエリでブログ記事とサンプル用の記事の出し分け

この辺のGatsbyのAPIや、source、transformerなどを調べるとかなりわかった気になる(理解度は深まったはず)、色々できそうだなと感じたのでアイデア考えるのが楽しくなる

最後に、コードハイライトのCSSも修正しているけど、実際の差分は下記PullRequestに

コードハイライト、Markdownの見た目調整 by swfz · Pull Request #1694 · swfz/til