gatsby-image から gatsby-plugin-image への移行

1. 概要

Gatsby + Shopify のサイトを廃止される gatsby-image から gatsby-plugin-image へ移行することにより、性能が向上するか確認してみました。gatsby-plugin-image は高い性能を維持しつつ、複数のサイズと形式の画像を作成するという面倒な処理を替わりにやってくれるGatsby の画像のプラグインです。

残念ながら、小さなサンプルでは性能向上しましたが、Shopifyサイトでは性能低下(gatsby-background-imageは暫定対応なのでメジャーアップデートを待つべきかも)や不具合が発生したので、もう少し調べたいと思います。とりあえず、方法だけ記載しておきます。

2. gatsby-image から gatsby-plugin-image への変更点

詳細はGatsby公式の Migrating from gatsby-image to gatsby-plugin-image にあります。以下、抜粋の和訳です。

  1. 構文が大幅に変わった
    • 移行に役立つ codemod が利用可能
    • エクポートの方式がデフォルトから名前つきに変更
      • import Img from “gatsby-image” ⇛ import { GatsbyImage } from “gatsby-plugin-image”
    • GraphQLの変更
      • 旧:childImageSharp { fixed { …GatsbyImageSharpFixed } }
      • 新:childImageSharp { gatsbyImageData(layout: FIXED) }
    • コンポーネントの変更
      • 旧:<Img fixed={data.file.childImageSharp.fixed} />
      • 新:<GatsbyImage image={data.file.childImageSharp.gatsbyImageData} />
  2. APIの変更
    • 画像種類の fluid の廃止 ⇛ fullWidth
    • 画像種類の constrained (レスポンシブ画像)の導入
    • maxWidth 及び maxHeight は廃止予定
      • constraind 及び fixed 画像には width height を使う
      • fullWidth 画像には breakpoints を使う
    • aspectRatio // アスペクト比を数値又は分数で指定する新しい引数
    • formats // 配列で画像形式を指定する。例: [AUTO, WEBP, AVIF]
    • オブジェクト内にネストされたオプション(詳細はこちら
      • grayscale transformOptions 内で指定する
      • pngQuality pngOptions 内の quality
  3. 重大な変更
    • GatsbyImage はクラスコンポーネントでないので、継承できなくなったが代わりにコンポジションが使用できる
    • fluid 画像は存在しなくなったので fullWidth を使うようにする。そちらでは maxWidth 又は maxHeight は使わない
    • art direction APIが変更(詳細
    • コンポーネントはオブジェクトの分割代入ができなくなった
      • <GatsbyImage image={{ src: example.src, srcSet: “, width: 100 }} /> // これはできない

3. 移行方法

3-1. プラグインのインストール

$ npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp
  • 私のとあるプロジェクトでは後ろの2個はインストール済で、gatsby-plugin-image のインストールだけが必要でした。

3-2. gatsby-config.js の編集

module.exports = {
  plugins: [
    `gatsby-plugin-image`,
    `gatsby-plugin-sharp`,
    `gatsby-transformer-sharp`,
  ],
}
  • 私のとあるプロジェクトでは`gatsby-plugin-image`, だけ追加が必要でした。

3-3. codemod の実行

  • 以下は私のとあるプロジェクトでの実行結果を後ろに示しています。
$ npx gatsby-codemods gatsby-plugin-image

npx: 248個のパッケージを19.371秒でインストールしました。
 You have not provided a target directory to run the codemod against, will default to root.
 Executing command: jscodeshift --ignore-pattern=/node_modules/ --ignore-pattern=/.cache/ --ignore-pattern=/public/ --extensions=jsx,js,ts,tsx --transform C:\Users\pitan\AppData\Roaming\npm-cache_npx\13764\node_modules\gatsby-codemods\transforms\gatsby-plugin-image.js ./
 Processing 70 files… 
 Spawning 7 workers…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 Sending 10 files to free worker…
 It appears you're passing a variable to your image component. We haven't changed it, but we have updated it to use the new GatsbyImage component. Please check src\components\ProductTile\index.js manually.
 maxWidth is no longer supported for fluid (now fullWidth) images. We've updated the query in src\fragments.js to use a constrained image instead.
 maxWidth is no longer supported for fluid (now fullWidth) images. It's been removed from your query in src\components\ExpandingCards\Card\index.js.
 maxWidth is no longer supported for fluid (now fullWidth) images. It's been removed from your query in src\context\ProductContext.js.
 All done.
 Results:
 0 errors
 61 unmodified
 0 skipped
 9 ok
 Time elapsed: 23.838seconds
  • 上記実行により、いくつかのファイルが書き換えられました。例を示します。
import { graphql } from 'gatsby';

export const productFields = graphql`
  fragment ShopifyProductFields on ShopifyProduct {
    shopifyId
    title
    description
    images {
      id
      localFile {
        childImageSharp {
          fluid(maxWidth: 300) {               // ここが変更
            ...GatsbyImageSharpFluid_withWebp  // ここが変更
          }
        }
      }
    }
  }
`;

↓

import { graphql } from 'gatsby';

export const productFields = graphql`
  fragment ShopifyProductFields on ShopifyProduct {
    shopifyId
    title
    description
    images {
      id
      localFile {
        childImageSharp {
          gatsbyImageData(width: 300, layout: CONSTRAINED) // ここが変更
        }
      }
    }
  }
`;

4. 確認と手動での変更

  • ツールが書き換えたところは、注意深く確認し、以下などは対応したほうがよいようです。
  • 私のプロジェクトも例えば次のように変更が必要でした。

4-1. StaticImage の使用

  • 静的クエリ(useStaticQuery など)をしていた部分は、StaticImage に変更します。
変更前
import React from 'react';
import Img from 'gatsby-image';
import { useStaticQuery, graphql } from 'gatsby';

export const Logo = () => {
  const data = useStaticQuery(graphql`
    {
      file(relativePath: { eq: "over40webshop.png" }) {
        childImageSharp {
          fixed(width: 160) {
            ...GatsbyImageSharpFixed_withWebp
          }
        }
      }
    }
  `);

  return <Img fixed={data.file.childImageSharp.fixed} />;
};
変更後
import React from 'react';
import { StaticImage  } from "gatsby-plugin-image";

export const Logo = () => <StaticImage src="../../images/over40webshop.png" alt="Over 40 Web Shop icon" width={160} />;

4-2. gatsby-background-image は gbimage-bridge を併用する

こちらは例えばこんな感じで変更します。

変更前
import React from 'react';
import BackgroundImage from 'gatsby-background-image';
import { graphql } from 'gatsby';
import styled from 'styled-components';
import Layout from '../components/layout';
import SEO from '../components/seo';

const StyledBackground = styled(BackgroundImage)`
// 省略
`;

const BlackOverlay = styled.div`
// 省略
`;

const ContentBox = styled.div`

// 省略
`;

const IndexPage = props => (
  <Layout>
    <SEO title="Home" />
    <StyledBackground fluid={props.data.indexImage.childImageSharp.fluid}>
      <BlackOverlay>
        <ContentBox>
          <h1>London</h1>
          <h2>27 Apr, 2004</h2>
        </ContentBox>
      </BlackOverlay>
    </StyledBackground>
  </Layout>
);

export default IndexPage;

export const pageQuery = graphql`
  query {
    indexImage: file(relativePath: { eq: "london.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 2560) {
          ...GatsbyImageSharpFluid
        }
      }
    }
  }
`;
変更後
import React from 'react';
import BackgroundImage from 'gatsby-background-image';
import { getImage } from 'gatsby-plugin-image';
import { convertToBgImage } from 'gbimage-bridge';

import { graphql } from 'gatsby';
import styled from 'styled-components';
import Layout from '../components/layout';
import SEO from '../components/seo';

const StyledBackground = styled(BackgroundImage)`
// 省略
`;

const BlackOverlay = styled.div`

// 省略
`;

const ContentBox = styled.div`

// 省略
`;

const IndexPage = props => {
  const image = getImage(props.data.indexImage);
  const bgImage = convertToBgImage(image);

  return (
    <Layout>
      <SEO title="Home" />
      <StyledBackground {...bgImage}>
        <BlackOverlay>
          <ContentBox>
            <h1>London</h1>
            <h2>27 Apr, 2004</h2>
          </ContentBox>
        </BlackOverlay>
      </StyledBackground>
    </Layout>
  );
};

export default IndexPage;

export const pageQuery = graphql`
  query {
    indexImage: file(relativePath: { eq: "london.jpg" }) {
      childImageSharp {
        gatsbyImageData(
          width: 2560
          placeholder: BLURRED
          formats: [AUTO, WEBP, AVIF]
        )
      }
    }
  }
`;
画像
多少速度も向上した