Bun.Image で画像処理を sharp から乗り換えた記録
Bun v1.3.14 で追加された Bun.Image API を使い、sharp ベースの画像リサイズ処理をゼロ依存に置き換えた手順と実測結果をまとめた
ポートフォリオのOGP画像生成で sharp を使っていたのだが、VPS を作り直すたびに node_modules 以下で libvips のネイティブビルドが走り、たまにコケる。Bun v1.3.14 で Bun.Image が入ったと聞いて、これを機に乗り換えた。
sharp の何がつらかったか
sharp 自体は優秀なライブラリで、機能に不満はなかった。問題はビルド周りに集中している。
npm install sharp は裏で libvips のプリビルドバイナリを落としてくる。ローカルの macOS では一瞬だが、CI の Ubuntu やコンテナ内だとアーキテクチャ不一致で落ちることがある。--platform や --arch のフラグを付けて回避するのだが、半年後の自分はそのフラグの存在を忘れている。Docker のマルチステージビルドで node_modules をコピーする際に libvips の .so が欠けて本番で Error: Cannot find module '../build/Release/sharp-linux-x64.node' を踏んだこともある。
やっていることはリサイズと WebP 変換だけなので、ランタイム組み込みで済むならそれに越したことはない。
乗り換えの手順
移行対象は、アップロードされた画像を 1200×630 にリサイズして WebP に変換する処理。sharp 時代のコードはこうだった。
import sharp from "sharp";
const buffer = await sharp(input)
.resize(1200, 630, { fit: "cover" })
.webp({ quality: 80 })
.toBuffer();
Bun.Image への書き換えはほぼ 1 対 1 で対応する。
const buffer = await new Bun.Image(input)
.resize(1200, 630, { fit: "fill" })
.webp({ quality: 80 })
.bytes();
変わった点は 3 つ。
importが消える。Bun.Imageはグローバルに存在するfitの値が違う。sharp のcoverに相当するのはfill。公式リファレンスではfitに"fill"か"inside"を指定する- 終端メソッドが
.toBuffer()→.bytes()に変わる。ほかに.buffer()、.blob()、.write(path)もある
Bun.file() から直接チェインする書き方もできる。
await Bun.file("photo.jpg")
.image()
.resize(1200, 630, { fit: "fill" })
.webp({ quality: 80 })
.write("og.webp");
ファイルの読み込みからエンコードまで 1 行で書けるのは気持ちがいい。
実測してみた結果
公式ブログに sharp 0.34.5 との比較ベンチマークが載っているが、自分の環境でも軽く測った。手元にあった 3.2MB の PNG(1920×1080)を 800×600 の WebP に変換する処理を 50 回ループして平均を取った。
体感としては、リサイズ自体は sharp と大差ない印象。2〜3 割速いかな、という程度。ただしメタデータの取得は明らかに速い。sharp だと .metadata() に数十ミリ秒かかっていたのが、Bun.Image だとほぼゼロに感じる。公式の計測では metadata() で約 70 倍の差が出ている。画像のサイズチェックを先にやってからリサイズする、というパターンでは地味に効いてくる。
移行で踏んだ落とし穴
AVIF と HEIC のエンコードは macOS と Windows でしか動かない。Linux の VPS で AVIF 出力しようとして ERR_IMAGE_FORMAT_UNSUPPORTED を食らった。公式ドキュメントにちゃんと書いてあるのだが、読み飛ばしていた。JPEG・PNG・WebP は全プラットフォームで動く。
もうひとつ、sharp にある rotate() の任意角度回転は Bun.Image にはない。rotate(90), rotate(180), rotate(270) の 90 度刻みだけ。EXIF の向き補正は autoOrient オプションで対応できるが、斜め補正のような処理が必要なら sharp を残すしかない。
GIF のデコードも最初のフレームだけ。アニメーション GIF を扱うなら sharp のままにしておくべき。
結局どこまで置き換えられるか
自分の用途——OGP 画像生成、サムネイル作成、アップロード画像の WebP 変換——はすべて Bun.Image で置き換えられた。package.json から sharp が消え、node_modules のサイズが目に見えて減った。Docker イメージのビルドでネイティブモジュール周りを気にしなくてよくなったのが一番大きい。
一方で、sharp の libvips が持っている機能の厚みにはまだ届いていない。任意角度回転、アニメーション GIF/WebP、Linux での AVIF エンコードあたりは現時点では対象外。全部を Bun.Image に寄せるのではなく、合うところから置き換えて、足りない部分は sharp を残す——という進め方が現実的だと思う。
検証環境: Ubuntu 22.04 / Bun 1.3.14 / sharp 0.34.5 / 2026年7月