Bun.Image — 高速ネイティブ画像処理パイプライン
Bun v1.4+で導入されたBun.Imageの仕組みを解説。sharp不要のネイティブ実装、SIMDハードウェアアクセラレーション、オフスレッド実行とAstro連携。
画像のリサイズ、トリミング、WebP/AVIF形式への変換、動的OGP(OpenGraph)画像の生成は、WebアプリケーションやSSRサーバーにおいて最もCPU負荷の高い処理の1つです。
これまでNode.jsエコシステムでは sharp が標準として広く使われてきました。しかし、sharp はC++ネイティブアドオン(libvips)を必要とし、アーキテクチャ間の依存関係やDockerコンテナ肥大化の要因となっていました。
Bun v1.4.0 から導入された Bun.Image は、ランタイムに組み込まれた高速ネイティブ画像処理パイプラインであり、これらの課題を根本から解決します。
flowchart LR
NodeApproach["Node.js + sharp<br/>(C++ Addon, 重いバイナリ, node-gyp)"] -.->|置き換え| BunApproach["Bun.Image<br/>(依存ゼロ, SIMD高速化, オフスレッド)"]
style NodeApproach fill:#ffeef0,stroke:#d73a49,stroke-width:1px
style BunApproach fill:#f0fff4,stroke:#2da44e,stroke-width:2px
1. 10歳でもわかる:Bun.Imageとは? (ELI5)#
ケーキ屋さんをイメージしてください:
- 従来の方法 (
sharp/ npm addon):ケーキにイラストを描くたびに、外部の専門パティシエ(C++ binding)を呼び、重い道具箱(native binaries)を持ってきてもらう必要があります。別の工房に移動すると道具が合わなくなることもあります。 - Bunの方法 (
Bun.Image):メインシェフ(Bunランタイム自身)がすでに超高速ロボットアーム(SIMDアクセラレーション)を備えています。外部の業者を呼ばなくても、材料を渡すだけで瞬時に綺麗なケーキが完成します。
Bun.Image は追加のnpmパッケージを一切インストールすることなく、高速に画像を変換・最適化できるBun内蔵の仕組みです。
2. Bun.Image の動作メカニズム#
Bun.Image はハードウェアレベルの最適化からWeb標準APIまでシームレスに設計されています:
flowchart TD
subgraph MainThread["JSメインスレッド (Non-blocking)"]
Req["Astro SSR / APIリクエスト"] --> Input["Input Buffer / Uint8Array / Blob / File"]
Input --> Chain["パイプライン構築 (Lazy)<br/>new Bun.Image(input).resize(1200, 630).webp()"]
Chain --> Await["終端メソッド呼び出し<br/>await pipeline.blob()"]
end
subgraph NativeWorker["オフスレッド ネイティブエンジン (Zig / C++ & SIMD)"]
Await --> Decode["並列デコード<br/>(libjpeg-turbo / spng / libwebp)"]
Decode --> SIMD["SIMD幾何学変換<br/>(Apple vImage / Highway SIMD)"]
SIMD --> Encode["ネイティブエンコード<br/>(WebP / PNG / JPEG)"]
end
subgraph OutputStage["Web標準出力"]
Encode --> BlobRes["MIMEタイプ付きBlob (image/webp)"]
BlobRes --> HTTP["Response(blob, { headers })"]
end
主な技術的特長:#
- ゼロ依存 & ネイティブアドオン不要:
NODE_MODULE_VERSIONの不一致やAlpine Dockerでのビルドエラーが解消されます。 - SIMDハードウェアアクセラレーション: x86_64/ARM LinuxではGoogle Highway SIMD、macOSではApple Accelerate
vImageを利用し、CPU性能を限界まで引き出します。 - オフスレッドでの遅延評価 (Lazy Evaluation):
new Bun.Image(input).resize(800, 600).webp()を呼び出した時点では、軽量な変換設定のみが保持されます。.blob()や.bytes()、.write()などの終端メソッドをawaitして初めて、バックグラウンドのネイティブスレッドで実際のデコード・変換・エンコードが実行されます(JSのイベントループをブロックしません)。
- Web標準
Responseとの親和性:.blob()出力には自動的に適切なMIMEタイプ(image/webp、image/pngなど)が付与されるため、AstroのSSRエンドポイントでそのままreturn new Response(blob)として返却できます。
3. Bun.Image の活用シーン#
| ユースケース | 実装内容 | 主なメリット |
|---|---|---|
| 動的OGP(アイキャッチ)生成 | 記事タイトルからリアルタイムでSNS共有画像を生成。 | satori と組み合わせてSVG→PNG/WebPをミリ秒単位で生成可能。 |
| Astro SSR オンデマンド最適化 | クライアントの画面幅に応じた画像リサイズ・WebP変換プロキシ。 | メインスレッドを阻害せずに転送量を大幅削減。 |
| ユーザー投稿アバター処理 | アップロードされた画像のトリミング・圧縮とS3/R2保存。 | ネイティブ速度で70〜80%の容量圧縮。 |
| ビルド時アセット最適化 | 静的サイトビルド時に src/assets/ 配下の画像を一括最適化。 | ビルド時間を大幅に短縮。 |
4. Astroでの実践:動的OGP画像エンドポイント#
以下は、AstroのAPIエンドポイントで Bun.Image を使ってOGP画像を生成する実装例です:
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ url }) => {
const title = url.searchParams.get('title') || 'Dopana Blog';
// 1. OGP用のSVGテンプレートを定義
const svg = `
<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0f172a"/>
<stop offset="100%" stop-color="#1e293b"/>
</linearGradient>
</defs>
<rect width="1200" height="630" fill="url(#bg)"/>
<circle cx="1100" cy="100" r="250" fill="#38bdf8" opacity="0.1" />
<text x="80" y="280" fill="#38bdf8" font-size="28" font-weight="bold" font-family="sans-serif">DOPANA TECH BLOG</text>
<text x="80" y="360" fill="#ffffff" font-size="52" font-weight="bold" font-family="sans-serif">${title}</text>
</svg>
`;
// 2. Bun.ImageでSVGからPNG Blobへ高速変換
const image = new Bun.Image(Buffer.from(svg));
const pngBlob = await image.resize(1200, 630).png().blob();
// 3. HTTPレスポンスを返却
return new Response(pngBlob, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
};ts[!TIP] PNGの代わりに
.webp({ quality: 80 })を指定すると、ファイルサイズをさらに約30%削減できます。
5. 一括画像最適化スクリプト例#
ビルド前後に静的アセットを一括で変換・最適化するCLIスクリプトも簡単に記述できます:
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
const assetsDir = './src/assets';
const files = await readdir(assetsDir);
for (const file of files) {
if (file.endsWith('.png') || file.endsWith('.jpg')) {
const inputPath = join(assetsDir, file);
const outputPath = join(assetsDir, `${file.split('.')[0]}.webp`);
const fileBuffer = await Bun.file(inputPath).arrayBuffer();
// 最大幅1920pxにリサイズしてWebP出力
await new Bun.Image(fileBuffer)
.resize(1920)
.webp({ quality: 85 })
.write(outputPath);
console.log(`Optimized: ${file} -> ${outputPath}`);
}
}ts6. Bun.Image vs sharp 比較#
| 項目 | sharp | Bun.Image (Bun 1.4+) |
|---|---|---|
| インストール | bun add sharp + ネイティブバイナリ | 不要(内蔵) |
| 依存関係サイズ | 約30MB〜50MB | 0 MB(追加なし) |
| CI/Docker構成 | glibc/muslの互換性設定が必要 | Bunバイナリ単体で即座に動作 |
| オフスレッド実行 | 対応 (libuvスレッドプール) | 対応 (ネイティブワーカースレッド) |
| SIMD対応 | libvips SIMD | Apple Accelerate vImage / Highway SIMD |
| Web Standard Response | Stream/Bufferの変換が必要 | .blob() / .bytes() で直接連携 |
[!NOTE] 複雑なカラープロファイル処理や高度な画像合成などのニッチな用途では依然として
sharpが適している場合があります。しかし、一般的なWeb用途(リサイズ、変換、OGP生成)の95%においては、Bun.Imageの方がシンプルかつ高速です。
まとめ#
Bun.Image は、オールインワンランタイムとしてのBunの強みを象徴する機能です。Astro SSRエンドポイントや画像変換パイプラインにおいて、余計な依存関係を持たずにネイティブの処理性能を手に入れることができます。