🖼️

Astroで、ローカルの画像を最適化する

2024/03/08に公開

ローカルにある画像を(動的に)最適化するのは難しい

importを使えない状況だってある

通常、Astroでローカルの画像を最適化する場合、importであらかじめ画像を静的に指定しておく必要があります。いつも画像の場所が一意に定まっているのであればいいのですが、動的に取得した画像を最適化したいこともあるかと思います。

ですが、astro:assetsでローカルにある画像を扱うのは意外と難しいです。例えば、以下のようなコードで画像を最適化しようとしても、出力されるのは最適化前の画像です。ファイルパスをstringで渡すような使い方は、astro:assetsではできないのです。

❌ 外部からjpgを渡して、最適化したい…
---
import {Picture} from "astro:assets";

interface Props{
  image: string; // スクリプトの呼出元から画像のファイルパスが渡される
}

const {image} = Astro.props;
---
<Picture
  src={image}
  alt="AVIF/WEBPに変換します"
  height="128"
  width="256"
  formats={['avif', 'webp']}
  />
  // ↑ 表示されるのは、呼出元から渡されたjpgのみ

公式のドキュメントにも、ローカル画像はimportすべしとあります。

それでもローカル画像を動的に最適化したい

Astro.globを使おう

力技ではありますが、Astro.glob (import.meta.glob)で条件に合う画像を部取得 → 与えられたファイルパス(string)に合致する画像を抽出する操作で解決します。

再利用性を考えて、画像抽出操作をするスクリプトを、.astroファイルとは別に用意します。

✅ image_optimizer.ts
// /public/images/内の画像を取得
const localImages = await import.meta.glob('/public/images/**/*', { eager: true });

// 与えられたファイルパスと一致する画像があれば、その画像をオブジェクトとして返す
export async function getImageObject(filePath: string) {
  const imageModule = localImages['/public' + filePath];
  if (imageModule) {
    return await (imageModule as any).default;
  }
  else {
    // エラーの際の処理
    throw new Error(`Image not found: ${filePath}`);
  }
}

まずは、上のスクリプトを指定し、.astroファイルからgetImageObject()関数をインポートします。

そして、getImageObject()関数を実行して取得した画像を、<Picture>に放り込んでみましょう。今度は無事に最適化されるはずです。

✅ index.astro
---
import {getImageObject} from "./image_optimizer";


interface Props{
  image: string; // スクリプトの呼出元から画像のファイルパスが渡される
}

const {image} = Astro.props;
const imageObj = getImageObject(image);
---

<Picture
  src={imageObj}
  alt="AVIF/WEBPに変換します"
  height="128"
  width="256"
  formats={['avif', 'webp']}
  />
  // ↑ 今度は、avifとwebpに変換された画像が表示されるはずです。

import.meta.glob('/public/images/**/*', { eager: true });で画像を全取得するのがパフォーマンス的に少し気になりますが、小規模なウェブサイトであればほぼ問題ないかとは思います。

Discussion