nextjs×reg-suitで画像が読み込まれない問題を解決する
結論
次のissueのコメントを参考にした。
リンク先で説明してることを少しアレンジして
↓こういうコンポーネントを作ってnext/imageをラップして、プロジェクト全体のnext/imageを使っているところを置き換える
import NextImage, { ImageProps } from 'next/image';
import { forwardRef } from 'react';
export let Image = NextImage;
const UnoptimizedImage = forwardRef<HTMLImageElement, ImageProps>(function Image({ src, ...props }, ref) {
return (
<img ref={ref} src={typeof src == "string" ? src : "src" in src ? src.src : src.default.src} {...props} />
);
}) as typeof NextImage;
export function unoptimizeNextImageForStorybook() {
Image = UnoptimizedImage;
}
そのあとstorybookのpreview.ts
でunoptimizeNextImageForStorybook
を読み込む
// .storybook/preview.ts
import { unoptimizeNextImageForStorybook } from '@/components/Image';
unoptimizeNextImageForStorybook();
こうするとstorybookの中ではnext/imageじゃなく普通のimgタグを使うようになる。
なぜ治るのかは知らないが、とにかくnext/imageを使わなければ問題が解決したのでヨシ
どんな問題が起きていたか
次の画像のように、reg-suitを実行したとき画像が読み込まれたり読み込まれなかったりが、ランダムに起きていた
試したこと
結論としては冒頭のことを実践すれば解決するのだが、メモとして試したことを残しておく
next/imageのdefaultPropsを変える
// .storybook/preview.ts
import Image from "next/image";
Image.defaultProps = { unoptimized: true }
上記コードでstorybook内でのみImageのdefaultPropsが変えられる
これをいくつかのpropsで試した。
next/imageの最適化処理が何かしらの悪さをしていると仮説を立てて、最適化系の処理をいろいろ無効化してみた。
unoptimized=trueにする
// .storybook/preview.ts
import Image from "next/image";
Image.defaultProps = { unoptimized: true }
問題は解決しなかった
loading=eagerにする
// .storybook/preview.ts
import Image from "next/image";
Image.defaultProps = { loading: "eager" }
問題は解決しなかった
loaderを変える
// .storybook/preview.ts
import Image from "next/image";
Image.defaultProps = { loader: ({src})=>src }
問題は解決しなかった
Object.definePropertyでnext/imageをモックする
next/imageを置き換える発想自体はあったので、Object.defineProperty
でnext/imageをモックしてみた
import * as NextImage from "next/image";
const OriginalNextImage = NextImage.default;
// Override next/image so that we can render images unoptimized in Storybook.
Object.defineProperty(NextImage, "default", {
configurable: true,
// @ts-ignore
value: (props) => <OriginalNextImage {...props} unoptimized />,
});
TypeError: Cannot redefine property: default
というエラーが出て、storybookが落ちた。
waitForで読み込まれるまで待ってみる
reg-suitを走らせる前にstorycapというライブラリを使ってスクショを取っているので、storycapで何か問題が起きているだろうと考えた。
storycapにはwaitForなるオプションがあり、そこでimgの読み込みを待つようなコードを書く
parameters:{
screenshot: {
waitFor: function waitForImage() {
const images: HTMLImageElement[] = Array.from(
document.querySelectorAll("img")
);
return Promise.all(
images.map((im) => new Promise((resolve) => (im.onload = resolve)))
);
}
}
}
storycapがタイムアウトし、そもそもスクショが取れなかった。
waitAsset=trueにする
前項と同様にstorycapに原因があると考え、storycapのオプションを変えてみた
storycapには waitAssets
なるオプションがある
parameters:{
screenshot: {
waitAssets: true,
}
}
デフォルトがtrueなので望みは薄い
問題は解決しなかった
まとめ
なぜ解決したのかはわからないが、とにかく解決したのでヨシ