Gatsby.jsの画像最適化プラグインのgatsby-imageを使おう！

2021/03/22追記：
[gatsby-plugin-image](https://www.gatsbyjs.com/plugins/gatsby-plugin-image/)という新しいプラグインが公開されました．この記事でも紹介しているようなgatsby-imageが抱えていたいくつかの問題を改善しているプラグインになります．そのため，今からgatsby-imageを利用するのはあまりオススメしませんが，備忘録としてこちらの記事は残しておきます．

# はじめに
https://takumon.com/2018/10/21/#gatsby-image 
@Takumon さんのwebページの記事を参考いたしました。[gatsby.js](https://www.gatsbyjs.org)のプラグインがとても簡潔にまとめてあり勉強になりました。感謝を申し上げます。

今回は、[gatsby.js](https://www.gatsbyjs.org)における画像の最適化を行ってくれるプラグイン[gatsby-image](https://using-gatsby-image.gatsbyjs.org)について、「**実装したこと**」「**やりたかったこと**」「**できなかったこと**」をまとめた記事です。

# 開発環境
#### ◯ 基本環境
- **MacOS** 10.14
- **Gatsby.js** 2.0.76 ( https://www.gatsbyjs.org )
- **React.js** 16.6.3 ( https://reactjs.org )

#### ◯ プラグイン等
- **gatsby-image**
- **gatsby-plugin-sharp**
- **gatsby-transformer-sharp**
- **gatsby-source-filesystem**

# gatsby-imageについて
## [公式サイト](https://using-gatsby-image.gatsbyjs.org)を読む
### 原文ママ
> - Loads the optimal size of image for each device size and screen resolution
- Holds the image position while loading so your page doesn’t jump around as images load
- Uses the “blur-up” effect i.e. it loads a tiny version of the image to show while the full image is loading
- Alternatively provides a “traced placeholder” SVG of the image.
- Lazy loads images which reduces bandwidth and speeds the initial load time
- Uses WebP images if browser supports the format

### 日本語訳
- 各デバイスのサイズと画面解像度に最適なイメージサイズを読み込みます
- 画像の読み込み中に画像の位置を保持します
- 画像全体が読み込まれている間に表示する画像の小さな画像を読み込みます。
- レイジーは画像をロードして帯域幅を減らし、初期ロード時間を短縮します
- ブラウザーがその形式をサポートしている場合、WebPイメージを使用します。

### ※超意訳※
#### **画像最適化周りの面倒臭いことは、プラグインが全部いい感じにやってくれます**


## 実装部分
```javascript:gatsby-config.js
module.exports = {
    plugins: {   
        {
        resolve: `gatsby-source-filesystem`,
            options: {
                name: `images`,
                path: `${__dirname}/src/images`,
            },
        },
        `gatsby-transformer-sharp`,
        `gatsby-plugin-sharp`,
    
        ...(以下省略)
    }
}
```
gatsby-config.jsでは、画像を入れておくディレクトリまでのパスを指定しています。gatsby-imageはプラグインではなく、gatsby-transformer-sharpとgatsby-plugin-sharpに依存したライブラリなので、plugins:{}には記述する必要はありません。

```react:index.jsx
import React from 'react'
import { Link, StaticQuery, graphql } from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../components/layout'  // 詳細は割愛

const IndexPage = () => (
    <StaticQuery
        query={query}
        render={ data => (
            <Layout>
                <div>
                    <Img fixed={data.file.childImageSharp.fixed}/>
                </div>
                <Link to="/page-2/">Go to page 2</Link>
            </Layout>
        )}
    />
)

export default IndexPage

const query = graphql`
    query {
        file(relativePath: {eq: "logo.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
```
これが最初の実装。GraphQLでローカルファイル検索を行って、検索に合致した画像を表示するといった一連の手順をシンプルに実装しています。

## やりたかったこと
1. 複数画像の表示
2. 様々な記述方法で対応
3. パス指定のみで表示できるように

### 1. 複数画像の表示
```javascript
const query = graphql`
    query {
        file(relativePath: {eq: "logo.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
```
ここの部分において、画像を1つしか指定できていないことがわかります。
「1ページあたり1枚しか画像を使えないの…」みたいな疑問にぶち当たったのでGatsby.jsの公式レファレンスを読んでみました。
https://www.gatsbyjs.org/docs/graphql-reference/#aliasing
公式サイトのレファンレンスに書いてある**Aliasing**を用いれば、一度に複数のファイルを要求することができそう。と言うことで実装します。

```react:index.jsx
import React from 'react'
import { Link, StaticQuery, graphql } from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../components/layout' // 詳細は割愛


const IndexPage = () => (
    <StaticQuery
        query={query}
        render={ data => (
            <Layout>
                <div>
                    <Img fixed={data.logo1.childImageSharp.fixed}/>
                    <Img fixed={data.logo2.childImageSharp.fixed}/>
                </div>
                <Link to="/page-2/">Go to page 2</Link>
            </Layout>
        )}
    />
)

export default IndexPage

const query = graphql`
    query {
        logo1:file(relativePath: {eq: "logo.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        },
        logo2:file(relativePath: {eq: "logo2.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
```

<img width="600" alt="gazo.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/2a4ac074-dc69-bf67-52bc-41bfc31a7958.png"/>

無事に表示できました。(今回は名前を変えただけで同じ画像を使っています)
aliasingでは自由に名前を指定できるみたいですね。今回はlogo1とlogo2と言う名前を使いましたが、わかりやすい自分なりの名前をつけるとミスが起こらなくて済みそうです。


### 2. 様々な記述方法で対応
```react
const IndexPage = () => (
    <StaticQuery
        query={query}
        render={ data => (
            <Layout>
                <div>
                    <Img fixed={data.logo1.childImageSharp.fixed}/>
                    <Img fixed={data.logo2.childImageSharp.fixed}/>
                </div>
                <Link to="/page-2/">Go to page 2</Link>
            </Layout>
        )}
    />
)
```
ここに書いてあるようにStaticQueryを使って描画していますが、もう少し直感的に書きたい気持ちがありました。ってことで別の記述方法で実装してみます。

```react:index.jsx
import React from 'react'
import { Link, graphql } from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../components/layout'

export default props => {
        return <Layout>
                <div>
                    <Img fixed={props.data.logo1.childImageSharp.fixed}/>
                    <Img fixed={props.data.logo2.childImageSharp.fixed}/>
                </div>
                <Link to="/page-2/">Go to page 2</Link>
            </Layout>
}

export const query = graphql`
    query {
        logo1:file(relativePath: {eq: "logo.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        },
        logo2:file(relativePath: {eq: "logo2.jpg"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
```

<img width="600" alt="gazo.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/2a4ac074-dc69-bf67-52bc-41bfc31a7958.png"/>

よしよし、表示できている。
Gatsby.jsではStaticQueryを使わない場合は、propsの中に自動的に格納されます。fixedの中身が少し変わっているので注意したいですね。全体的なコード量も少なくなっているのでいい感じです。

### 3. パス指定のみで表示できるようにしたい
これが最後の挑戦。「GraphQLのクエリをいちいち書いているのなんてめんどくせぇ！　俺はパス指定だけで最適化したいんじゃ！」となったので試してみました。
しかしながら，私が目指していた実装ではうまくいかなかったため，そのケースを共有したいと思います．

※以下の情報は参考情報として受け取ってください※

パスによる指定はjavascriptのテンプレートリテラル周りでうまくいきませんでした。
やりたかったこと↓

```Img.jsx
import * as React from 'react'
import {graphql, StaticQuery} from 'gatsby'
import Image from  'gatsby-image'

const getQuery = path => {
    return graphql`
    query {
        file(relativePath: {eq: "${path}"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
}

export default () => {
    return <StaticQuery
        query={ getQuery("logo.jpg") }
        render={data => (
            <Image fixed={data.file.childImageSharp.fixed}/>
        )}
    />
}
```
パスを指定するだけで動的にGraphQLクエリを生成し、ローカルにある画像の問い合わせを行います。しかしながら、これには弱点があります。
`graphql`の引数は`TemplateStringsArray`という型であり、これは **\`\`** (テンプレートリテラル)を使った文字列のことを指しています。そしてこれには大きな落とし穴があって、

```javascript
const getQuery = path => {
    return graphql`
    query {
        file(relativePath: {eq: "${path}"}) {
            childImageSharp{
                fixed(width: 200) {
                    ...GatsbyImageSharpFixed
                }
            }
        }
    }
`
}
```

テンプレートリテラル内で式展開を行う場合、**返ってくるのは`string型`に変換されたものになってしまうのです**。したがって、関数`graphql`の引数の型が一致せずエラーになってしまう…

解決方法を探しに探したのですが、`TemplateStringsArray型`を引数に格納しようとあがいても`string型`に変換されてしまうのでどうしようもできず…
結局は諦めることにしました。(←技術力不足)

#### ※2019/3/28追記
GatsbyJSのissueを探していたら、公式の方で動的な画像読み込みを推奨していないことがわかりました。(参照： https://github.com/gatsbyjs/gatsby/issues/6545 )色々やった結果はじき出されたエラーコードが次の画像の通り
<img width="1035" alt="スクリーンショット 2019-03-28 18.32.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/657e3293-07c8-6908-1e10-f8c11909c414.png">

つまり、`graphql`タグ付きテンプレートリテラルを使ったファイル問い合わせは**コンパイル時限定**とのこと。それでも使いたいならbabelの設定を黒魔術的にカスタムするか、GatsbyのOSSにプルリク送ってねという感じでした。

Gatsbyは静的サイトジェネレータなのでしょうがないです。静的画像の処理が簡単なのはありがたい限りなので、動的な画像読み込みは自分で最適化しましょう。

#### ※2020/2/27追記
私がよく参考にしているデベロッパの方が，パス指定で実現可能なコンポーネントを作成されています．
[パスを渡せばgatsby-imageで画像を表示してくれるコンポーネントの作成方法](https://takumon.com/simple-gatsby-image-wrapper)

具体的には，`gatsby-source-filesystem`で指定フォルダ内の全ての画像を取り寄せ，その中から指定したパスにマッチする画像を取り出すというものです．
荒技ではありますが，やりたいことができています！素晴らしい！
そのため，今回の記事で紹介した内容の一部は参考情報として受け取ってもらえれば幸いです．


# まとめ
色々ありましたが、gatsby-imageは面倒臭い画像最適化処理を全部やってくれる最高なライブラリです。この記事がgatsby.jsを使う際の参考になればと思います。

ここまで読んでくださり、ありがとうございました！