結論から言うと、画像圧縮の定番「TinyPNG」を商用で使い続けるのがもったいなくなったので、同じようなことを sharp でローカル完結できる画像圧縮ツールを自作して、npm と GitHub に MIT ライセンスで公開しました。
パッケージ名は gulp-sharp-compress です(ソースは GitHub にあります)。名前のとおり gulp プラグインですが、v1.1.0 からは gulp なしでも使えるようにしてあります。
この記事では、なぜ作ったか・インストール方法・中で何をやっているか(仕組み)・gulp での使い方・gulp を使わない使い方、をまとめます。
なぜ作ったか(TinyPNG課金問題)
TinyPNG / TinyJPG は mozjpeg などを使った、とてもよくできた画像圧縮サービスです。普段使いには文句なしですが、無料で使える枠は月およそ500枚で、それを超える商用利用はAPIの従量課金か有料プランが必要になります。受託でサイトを量産していると、この枚数はすぐに超えます。
そして、やっていることの中心は「JPEGはmozjpeg、PNGは減色(パレット量子化)で軽くする」です。これは sharp(内部は libvips)を使えば、自分のマシンの中だけで完結できます。だったら自前でやってしまえば、枚数は無制限・追加料金ゼロ・画像を外部にアップロードする必要もない。そう考えて作りました。
インストール
npm install gulp-sharp-compress
必要なのは Node.js 18 以上です。圧縮エンジンの sharp は npm install 時にプラットフォーム用のバイナリが入るので、別途のネイティブビルドはいりません。gulp は peerDependencies(任意)なので、後述の compressBuffer だけ使うなら gulp 自体は無くても動きます。
中で何をやっているか(仕組み)
圧縮の本体は sharp です。フォーマットごとに、定番のエンコーダを使い分けています。
- JPEG → mozjpeg(TinyPNG/Squoosh と同系統)
- PNG → パレット量子化(pngquant 相当の減色)
- WebP → libwebp
- AVIF → libaom
単にエンコードするだけでなく、Web制作で実際にハマりがちな処理もパイプラインに入れてあります。
- 向きの自動補正: EXIF の回転情報を先に画素へ焼き込んでからエンコードします。これをやらないと、メタデータを剥がした瞬間に縦写真が横倒しになります(自分が実際に踏んだ事故です)。
- メタデータ除去: EXIF/GPS などを既定で削ります(色が重要な画像向けに ICC プロファイルだけ残すオプションもあります)。
- 任意のリサイズ: maxWidth / maxHeight で上限を指定できます(拡大はしません)。
- 賢くスキップ: 同じフォーマットで再エンコードして逆に大きくなった場合は、元の画像をそのまま返します(戻り値の skipped が true になります)。
- アニメGIF / WebP は全フレームを保持します(先頭1枚に潰しません)。
つまり「TinyPNGの代わり」というより、受託のWeb制作で毎回やっている画像まわりの処理を一通り入れた道具、というのが実態に近いです。
gulp での使い方
gulp-imagemin の差し替え(drop-in replacement)として使えます。
import gulp from 'gulp';
import compress, { webp, avif } from 'gulp-sharp-compress';
// そのままのフォーマットで圧縮
gulp.src('src/images/**/*.{jpg,png}', { encoding: false })
.pipe(compress({ quality: 80 }))
.pipe(gulp.dest('dist/images'));
// WebP に変換して出力
gulp.src('src/images/**/*.{jpg,png}', { encoding: false })
.pipe(webp({ quality: 80 }))
.pipe(gulp.dest('dist/images'));
1点だけ注意で、Gulp 5 はバイナリファイルに { encoding: false } が必要です(つけ忘れると画像が壊れます)。webp / avif のほかに jpeg / png のヘルパーもあり、format オプションで明示的に変換先を指定することもできます。
gulp がなくても使える(compressBuffer)
gulp 前提のプラグインだと、Next.js のスクリプトやサーバーレス関数の中では使いにくいです。そこで v1.1.0 で、Buffer を1枚渡すと圧縮済みの Buffer を返す compressBuffer(input, options) を足しました。これは gulp 非依存で、素の Node スクリプトでも他のビルドツールでも動きます。
import { readFile, writeFile } from 'node:fs/promises';
import { compressBuffer } from 'gulp-sharp-compress';
const input = await readFile('photo.png');
// AVIF に変換
const { data, originalSize, compressedSize } = await compressBuffer(input, {
format: 'avif',
quality: 60,
});
await writeFile('photo.avif', data);
console.log(originalSize, '→', compressedSize);
戻り値は { data, format, originalSize, compressedSize, skipped } です。向きの補正・メタデータ処理・リサイズ・バリデーションは gulp パイプラインとまったく同じ挙動になります。ちなみに、このコーポレートサイトのブログのアイキャッチも、この compressBuffer で webp 化しています。
実際どれくらい軽くなるか
手元で1枚(PNG・約1.3MB)を compressBuffer に通してみた結果です。
- PNG 圧縮(同フォーマット): 約1.3MB → 約334KB(-74%)
- WebP 変換: 約35KB(-97%)
- AVIF 変換: 約18KB(-99%)
削減率は元画像によって変わりますが、WebP / AVIF まで使うと、写真でもかなり小さくできます。
ライセンスとリンク
MIT ライセンスなので、商用利用・改変・再配布すべて自由です。npm / GitHub に置いてあります。
受託でサイトを作るなら画像最適化は避けて通れません。外部サービスに毎月払う代わりに、ローカルで完結する道具を1つ持っておくと地味に楽になります。よければ使ってみてください。バグや要望は GitHub の Issue へどうぞ。


