Gatsbyで作っているブログサイトで、Tagの数が多くなってきたのでTagをまとめたカテゴリ単位の表示をさせたい
JSONファイルに適当なマッピングを持たせてそれを参照させたいというようなケースが発生したのでやってみた
普通にJSONを読み込んでも良いが、せっかくGatsbyで作っているのでJSONからGatsbyのNodeとして扱いGraphQLからクエリできるようにしてみる
今回はgatsby-transformer-json
を使う
install
yarn add gatsby-transformer-json
設定
const plugins: GatsbyConfig["plugins"] = [ + `gatsby-transformer-json`, + { + resolve: `gatsby-source-filesystem`, + options: { + path: `${__dirname}/content/definitions`, + name: `definitions`, + },
source-filesystemで特定ディレクトリ以下のファイルを読めるようにした
今回は定義ファイルなのでdefinitions
という名前にした
マッピングファイル
- content/definitions/categories.json(一部抜粋)
[ { "name": "Cloud", "tags": [ "AWS", "GoogleCloud", "Cloudflare" ] }, { "name": "エディタ", "tags": ["IntelliJ", "VS Code", "Vim"] } ]
とりあえずこの形式にした
ドキュメントや他記事を見る感じ、SchemaをカスタマイズしてGraphQLでタグの文字列とカテゴリを一発取得みたいなことも可能そうに見えたが
ぱっとできそうなイメージが湧かなかったのでまずはマッピングだけを用意し、データ取得するページ側で愚直に回してひもづけるようにした
型定義の生成
これでdevサーバを起動すれば関連する型定義は自動で生成してくれる
あらためて感じたがJSONの中身をみてこんな感じでしょっていうのをしっかり出してきてくれるので良い
Node名はCategoriesJson
になる
これはcategories.json
というファイル名とtransformer-json
がJSONを扱うtransformerなのでっぽい(ドキュメントまで調べてない
別のファイルで適当な名前をつけて中身を書いて(hoge.json
とか)devサーバを起動するとHogeJson
というNodeができてファイルの中身に合わせた型定義が生成される
Tag一覧ページでカテゴリごとに分類する
かなり個別ケースになってしまうが下記のようなコンポーネントを実装した(切り貼りしているのでもしかしたら動かないかも)
import { graphql, PageProps, Link } from "gatsby" import kebabCase from "lodash/kebabCase" import React from "react" type SummarizedTag = { [key: string]: { count: number tags: { fieldValue: string | null; totalCount: number }[] } } type Tag = { fieldValue: string | null totalCount: number } type TagsWithCountProps = { tags: Tag[] } const TagsWithCount = ({ tags }: TagsWithCountProps) => { return ( <span className="flex flex-row flex-wrap gap-1"> {tags.map(tag => ( <Link key={tag.fieldValue} aria-label="tag" className="label" to={`/tags/${kebabCase(tag?.fieldValue || "")}/`}> {tag.fieldValue} ({tag.totalCount}) </Link> ))} </span> ) } const TagsPage: React.FC<PageProps<Queries.TagsQuery>> = ({ data }) => { const group = data.allMarkdownRemark.group const categories = group.reduce((acc, tag) => { const category = data.allCategoriesJson.edges.find(({ node }) => node?.tags?.includes(tag.fieldValue))?.node.name || "Other" const row = acc[category] || { count: 0, tags: [] } const newData = { count: row.count + tag.totalCount, tags: [...row.tags, tag] } return { ...acc, [category]: { ...newData } } }, {} as SummarizedTag) return ( <main className="h-full divide-y divide-zinc-100 bg-white p-4"> <h1 className="pb-4 text-2xl">Tags</h1> {Object.entries(categories) .sort((a, b) => (a[0] === "Other" ? 1 : b[1].count - a[1].count)) .map(([category, row]) => ( <div key={category} className="py-4"> <details open> <summary key={category}> {category} ({row?.count}) </summary> <TagsWithCount tags={row?.tags} /> </details> </div> ))} </main> ) } export const pageQuery = graphql` query Tags { site { siteMetadata { title } } allMarkdownRemark(limit: 2000) { group(field: { frontmatter: { tags: SELECT } }) { fieldValue totalCount } } allCategoriesJson(limit: 1000) { edges { node { name tags } } } } ` export default TagsPage
GraphQLクエリ
- allMarkdownRemarkのgroupでタグごとの件数を取得
- allCategoriesJsonでマッピングのリストを取得
集計
上記で取得したデータを元に愚直に集計するだけ
まだいくつか思うところはあるがタグだけが羅列されているよりかはいい感じになった
実装PullRequestは下記