Gatsby-Imageコンポーネントを作る

3 min読了の目安(約2900字TECH技術記事

前提

1ページに複数の画像があり、
全てwebp対応して、
かつ使いまわしたい。

律儀に

まずテンプレを持ってくる。

gatsby-starter-default@0.1.0 develop /Users/goqsystem_77/Desktop/my-gatsby-project

// インストール後
yarn start

さて、お出迎えするのは見慣れた宇宙服のおっさん。

src/components/image.jsをみてみるとこんなコードが書いてあります。

src/components/image.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"

const Image = () => {
  const data = useStaticQuery(graphql`
    query {
      placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
        childImageSharp {
          fluid(maxWidth: 300) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  `)

  if (!data?.placeholderImage?.childImageSharp?.fluid) {
    return <div>Picture not found</div>
  }

  return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
}

export default Image

gatsby-astronaut.pngを検索し、その画像を引っ張ってきているだけですね。
ファイル名を決め打ちしてますから、もちろん使いまわせるはずもありません。

webp対応

まず、上記だとwebpが出力ができてません。対応します。
といってもFragment名を変えるのみです。

src/components/image.js
...
query {
  placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
    childImageSharp {
      fluid(maxWidth: 300) {
        ...GatsbyImageSharpFluid_withWebp
      }
    }
  }
}
...

これでOKです。

fragment

GatsbyにはFragmentという機能がありまして、任意のフィールドをまとめることができるんですね。
今回だと、GatsbyImageSharpFluid_withWebpは以下のように展開されます。

fragment GatsbyImageSharpFluid_withWebp on ChildImageSharp {
  base64
  aspectRatio
  src
  srcSetWebp
  srcWebp
  srcSet
  sizes
}

このフラグメントですが、どこで定義されたのかというと、gatsby-transformer-sharp プラグインによって定義されています。
これはプロジェクトにデフォルトでインストールされています。

以下はドキュメントです。
https://www.gatsbyjs.com/plugins/gatsby-image/#fragments

propsで受け取る

webp対応はできました。では使いまわしです。
結論、以下のように書き換えます。

src/components/image.js
const Image = ({ assetUrl, alt }) => {
  const { allImageSharp } = useStaticQuery(graphql`
    query {
      allImageSharp {
        nodes {
          fluid(maxWidth: 300) {
            originalName
            ...GatsbyImageSharpFluid_withWebp
          }
        }
      }
    }
  `)

  return <Img fluid={allImageSharp.nodes.find(n => n.fluid.originalName === assetUrl).fluid} alt={alt} />
}

queryにoriginalNameを追加しました。これはファイル名を取得するフィールドです。
propsにassetUrlとaltを設定しました。使用側でデータを渡して、受け取ります。

あとは、findメソッドでoriginalNameとassetUrlが一致するものをフィルタリングし、fluidにアクセスします。

使用側は以下のようにします。

<Image assetUrl='....png' alt='...' />

おじさまだけではつまらないでしょうから、猫を追加しました。

src/pages/index.js
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
  <Image assetUrl='gatsby-astronaut.png' alt='astronaut' />
</div>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
  <Image assetUrl='neko.jpg' alt='neko' />
</div>

感想

いちいちクエリを作成するのではなく、再利用性があるととても幸せなことに気づきました。