Help us understand the problem. What is going on with this article?

Gatsby.jsの画像最適化プラグインgatsby-imageを触ってみてわかったこと。

はじめに

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

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

開発環境

◯ 基本環境

◯ プラグイン等

  • gatsby-image
  • gatsby-plugin-sharp
  • gatsby-transformer-sharp
  • gatsby-source-filesystem

gatsby-imageについて

公式サイトを読む

原文ママ

  • 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イメージを使用します。

※超意訳※

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

実装部分

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:{}には記述する必要はありません。

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. 複数画像の表示

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を用いれば、一度に複数のファイルを要求することができそう。と言うことで実装します。

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
                }
            }
        }
    }
`

gazo.png

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

2. 様々な記述方法で対応

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を使って描画していますが、もう少し直感的に書きたい気持ちがありました。ってことで別の記述方法で実装してみます。

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
                }
            }
        }
    }
`

gazo.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という型であり、これは `` (テンプレートリテラル)を使った文字列のことを指しています。そしてこれには大きな落とし穴があって、

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 )色々やった結果はじき出されたエラーコードが次の画像の通り
スクリーンショット 2019-03-28 18.32.37.png

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

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

まとめ

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした