Gatsbyで美少女フィギュアレビュー用Themeを作った

[Gatsby](https://www.gatsbyjs.com/) で美少女フィギュアレビュー用の [Gatsby Theme](https://www.gatsbyjs.com/docs/themes/) を作った話。

- ソース: [`gatsby-theme-figure-blog`](https://github.com/tenpaMk2/gatsby-theme-figure-blog/tree/main/theme)
- Demo: [gatsby-starter-figure-blog.netlify.app](https://gatsby-starter-figure-blog.netlify.app/)

見た目はこんな感じ↓。

![スクリーンショット 2023-04-13 19.02.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214979/081d3527-6636-5459-92cf-65ad5d3ede9c.png)

まぁ、美少女フィギュアレビュー用とは書いたけど、普通に写真全般に特化したThemeだと思ってもらって良い(いわゆる釣りタイトル)。

技術的な解説とかをしていくよ❗

## モチベ

そもそも、ブログを引っ越ししたかったのがきっかけ。

今のブログは [Hexo](https://hexo.io/) 製なんだけど、
細かいところのカスタマイズがしづらいんだよね。EJSにどうにも慣れなくて...。
探り探りでカスタマイズしたのも数年前で、もう全然覚えてないわけ。
で、秘伝のタレ化しちゃったから、いっそ一から作ろうと思って、別のSSGフレームワークをあさり始めた。

JekyllとかHugoとかもあったんだけど、Gatsbyに決めた。
人気もすごいし、Reactだし、表示が爆速だしで良さげだったため。

誤算だったのは、Gatsbyで美少女フィギュアレビューブログを書いてるやつはいなかったこと...なぜだ...。
皆さん頭良さそうな技術系ブログばっか作ってて、写真がメインのStarterとかThemeが意外とない❗
(自分の探し方がイマイチな可能性は大きいが。)

しょうがないから全部自分で作った。
作ってるうちに「自分だけで使うのももったいねえな...」と思い始めたので、Themeと [Starter](https://github.com/tenpaMk2/gatsby-starter-figure-blog/blob/main/README-ja.md) も作ったった。
皆さんも美少女フィギュアレビューブログ作ろう❗❗❗私も作ったんだからさ💢💢💢

## 機能

1. [画像をできるだけ大きく表示](#1-画像をできるだけ大きく表示)
2. [レスポンシブ](#2-レスポンシブ)
3. [Markdown(MDXじゃなく)](#3-markdownmdxじゃなく)
4. [Special hooks](#4-special-hooks)
5. [Tailwind CSSサポート](#5-tailwind-cssサポート)

他、細かいところは [Readme](https://github.com/tenpaMk2/gatsby-theme-figure-blog/tree/main/theme) 読んでね。

### 1. 画像をできるだけ大きく表示

↓みたいに記事の横幅いっぱいまで画像拡大するようにしてる。

![スクリーンショット 2023-04-13 19.46.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214979/bbd511a0-e8de-45d4-1761-dfff6b963a08.png)

フィギュアレビューブログで画像が小さいのは致命的なんだよね。
可能な限り画面いっぱいにかわいこちゃんを表示したい❗

ただ、縦位置で撮った縦長写真で横幅いっぱいにすると、PCで見た時にでかすぎるので、そこは抑えてある。
縦幅が最大でスクリーンの高さまでになるようにした。

`gatsby-remark-images` を使ってるなら、↓のようにすればよい。

```js
plugins: [
  {
    resolve: `gatsby-remark-images`,
    options: {
      wrapperStyle: (image) =>
        `max-width:${(image.aspectRatio * 100).toFixed(2)}vh`, // See [issue: Can't specify max-width](https://github.com/gatsbyjs/gatsby/issues/15578) .
    },
  },
]
```

ちなみに、 `max-height` を指定するとレイアウトが崩れちゃったんだよな...CSSは謎。

### 2. レスポンシブ

イマドキはレスポンシブじゃないとお話にならない。
弊ブログのGoogle Analyticsの結果が↓だけど、もう5割以上はスマホなんだよね。

![スクリーンショット 2023-04-13 20.34.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214979/bdc5b2c1-a699-0e8c-1fc4-1485df6441f9.png)

レスポンシブ対応にあたっては、CSSのFlexとGridが大活躍だった。
部分的に使うのではなく、むしろ全体のレイアウトに使うのが良い。

1. ヘッダ
2. メイン
3. サイドバー
4. フッタ

とあったときに、メインとサイドバーをFlexItemにすれば、
画面幅を小さくしたときに自動でサイドバーが回り込んでくれる。

このあたりは書くこと多すぎなので、あとで別途解説する...かも。

### 3. Markdown(MDXじゃなく)

Pure MarkdownとMDXでどっちを採用するかは結構迷った。

MDXにすると、React Componentが使えて、記事ごとの表現力が上がる。
ただ、それって当然React Componentに依存するわけで、
将来的にGatsbyから引っ越すときに枷になりそう...と思ったので却下。

あと、URLを `<` と `>` で囲った書き方ができなくなるのもちょっと嫌。ただの上位互換じゃないんかい❗

さらに、あれこれ調べてたら、Pure Markdownでも表現力は補える方法があった。[Special hooksでで解説。](#4-special-hooks)

### 4. Special hooks

『Special hooks』ってのは自分が勝手に作った言葉なので注意。
特定の条件のMarkdownの記述にフックして、任意のReact Componentを作る機能。

例えば、タイトルに `left` と `right` を持った2枚の画像が並んでいたら、
↓のような画像比較スライダーを作ったりできる。

![image-compare-slider.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214979/3d98fc8b-5d8c-c963-bcbb-9864b7c39fe0.png)

[実際に動いてるデモはここ。](https://gatsby-starter-figure-blog.netlify.app/%E7%94%BB%E5%83%8F%E6%AF%94%E8%BC%83%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%80%E3%83%BC%E3%83%86%E3%82%B9%E3%83%88/)

設計的には、gatsby-transformer-remarkが作るHTML抽象構文木(hast)と [`hast-util-to-jsx-runtime`](https://github.com/syntax-tree/hast-util-to-jsx-runtime) を利用する。
例えば、↑の画像比較スライダーの例だと↓のようになる。

1. hastをパースして、タイトルが `left` と `right` から始まる2枚の画像を探す。
2. ↑の画像を子に持つ `p` タグを `ImageCompareSlider` タグにする。
3. `hast-util-to-jsx-runtime` でhastをReact Elementに変換
   - `components` オプションで、 `ImageCompareSlider` タグを同名のReact Componentへ変換するようにしておく。

これも詳細は別途解説記事を作る...かも。

### 5. Tailwind CSSサポート

以前、Tailwind CSSは別のプロジェクトでいじってて使い勝手が良かったので、今回も使いたい。
...が、これが結構手間取った。

まず、Themeとか関係なく、GatsbyでTailwind CSSを使う方法は検索すると出てくる。
しかし、Themeでとなるとヒットしない...。

Gatsby Themeは結構特殊で、ThemeのComponentをShadowingしたときにもTailwind CSSが使えるようにする必要があるんだよね。
そうなると、ディレクトリ構成的には2箇所でTailwind CSSを使えるようにしなきゃならない。

最初はThemeとThemeを使う側の両方でTailwind CSSをセットアップしてた。
PostCSSの有効化も `tailwind.config.js` の用意も `gatsby-browser.js` の `global.css` の `import` も2箇所でやってた。
これでも正常に動いてたんだけど、ShadowingするComponentを増やしたら急にレイアウトが崩れだした❗

デバッグしたら、Tailwind CSSのClass定義が重複してて、media-queryなどの順番がめちゃくちゃになってた。
原因は `gatsby-browser.js` の `global.css` の `import` を2箇所でやってたのが悪さをしてた。
これが重複すると、Theme側とThemeを使う側でClass名が別々に定義されて、重複しちゃうらしい。
(これに気づくまでにめっっっっちゃめちゃ時間かかった...。)

結局、Tailwind CSSのセットアップはThemeを使う側に一本化することにした。
そちらでPostCSSを有効化して、 `tailwind.config.js` も `global.css` も用意してもらう。
ただ、Theme側で `tailwind.config.js` を記述したい部分もあったので、Theme側にも `tailwind.config.js` を用意して、Themeを使う側で `require` してもらうことにした。

Theme側の `tailwind.config.js` が↓。

```js
const path = require(`path`);

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // See [Tailwind CSS | Working with third-party tools](https://tailwindcss.com/docs/content-configuration#working-with-third-party-libraries) .
    path.join(
      path.dirname(require.resolve(`@tenpamk2/gatsby-theme-figure-blog`)),
      "**/*.{js,jsx,mjs,ts,tsx}"
    ),
  ],
  theme: {
    extend: {
      dropShadow: {
        title: [
          `0 1px 1px rgb(100% 100% 100% / .8)`,
          `0 2px 3px rgb(0% 0% 0% / .5)`,
          `0 0px 20px rgb(0% 0% 0% / 1)`,
        ],
      },
      typography: {
        DEFAULT: {
          css: {
            ".gatsby-resp-image-wrapper": {
              boxShadow: `0 0 1rem rgb(0% 0% 0% / .5)`,
              borderRadius: `0.5rem`,
              overflow: `hidden`,
            },
            // 中略
          },
        },
      },
    },
  },
  plugins: [require(`@tailwindcss/typography`)],
};
```

Themeを使う側の `tailwind.config.js` が↓。

```js
const defaultOptions = require(`@tenpamk2/gatsby-theme-figure-blog/tailwind.config.js`);

/** @type {import('tailwindcss').Config} */
module.exports = {
  ...defaultOptions,
  ...{
    content: [...defaultOptions.content, `./src/**/*.{js,jsx,mjs,ts,tsx}`],
  },
};
```

## おまけ: 完走した感想

疲　れ　た　。

2022年12月からGatsbyをいじりはじめて、今日までかかってしまった。
最終的に理解しなきゃいけない要素は↓。

1. Gatsby
2. Gatsby Theme
3. GraphQL
4. React
5. JavaScript
6. HTML
7. CSS
8. Tailwind CSS(typography含む)
9. npm(ディレクトリ構造、workspace、publish...)
10. Netlify
11. Git(submoduleによるMarkdownと画像だけ別リポジトリ管理)

さすがに学習コストが高すぎた...。本職は組み込み屋さんやぞ私は...。
でも楽しかったのでOK。

で、ここまでやっておいて実はまだ引っ越しはできてないんだよねw
Hexo用に書いてた記事を新ブログ用に手直ししないといけなくて、それが面倒。

あと何日で弊ブログは新ブログに移行できるのやら...乞うご期待。

<https://tenpamk2-blog.netlify.app/>