Skip to content

image-squeezer ​

Optimizes JPEG, PNG, WebP, AVIF, GIF and TIFF images with sharp, converts them to WebP, AVIF, JPEG or PNG and resizes them to several widths for srcset. Photos are turned upright from the orientation stored in their metadata. SVG and other files pass through.

sh
npm install --save-dev @zilero/gulp-image-squeezer

Usage ​

js
import { dest, src } from 'gulp';
import imageSqueezer from '@zilero/gulp-image-squeezer';

export const images = () =>
  src('src/images/**', { encoding: false })
    .pipe(imageSqueezer({ formats: ['original', 'webp', 'avif'], widths: [640, 1280] }))
    // hero.jpg → hero-640w.jpg, hero-640w.webp, hero-640w.avif, hero-1280w.jpg, …
    .pipe(dest('dist/images'));

Images are binary: read them with encoding: false. Gulp 5 reads files as UTF-8 by default, which corrupts binary data.

Without options, every image is optimized in its own format and keeps its name:

js
imageSqueezer();
imageSqueezer({ formats: 'webp', quality: 70 });
imageSqueezer({ quality: { webp: 75, avif: 50, png: 80 }, encoderOptions: { png: { palette: true } } });

Options ​

OptionTypeDefaultDescription
formatsImageFormat | ImageFormat[]'original''original', 'webp', 'avif', 'jpeg' or 'png', or a list of them. 'original', or the source's own format, re-encodes in that format and keeps the extension. One file per format and width.
widthsnumber[]keep the sizeWidths in pixels: hero.jpg becomes hero-640w.jpg, hero-1280w.jpg, … Widths larger than the image become one file at the image's own width.
qualitynumber | { jpeg?, webp?, avif?, tiff?, png? }encoder defaults1 to 100 for every lossy encoder, or per encoder. GIF ignores it; PNG takes it only with encoderOptions.png.palette: true.
encoderOptions{ jpeg?: JpegOptions, png?: PngOptions, webp?, avif?, gif?, tiff? }nonesharp's options per encoder, merged over the defaults and quality.
keepSmallerbooleantrueKeep the source when re-encoding it in its own format made it bigger.

Unknown keys (in encoderOptions too), an encoderOptions entry that is not an object, a quality outside 1–100 and empty formats or widths lists are an error when the plugin is created. Quality and EncoderOptions are exported types.

Encoder defaults ​

Tuned for the web: visually lossless, AVIF at a moderate effort because it encodes slowly, and PNG lossless.

EncoderSettings
jpegquality: 80, mozjpeg: true
pngcompressionLevel: 9, adaptiveFiltering: true
webpquality: 80, effort: 5
avifquality: 55, effort: 4
gifeffort: 10
tiffquality: 80

quality overrides the default quality, and encoderOptions override both.

PNG palette ​

sharp quantizes a PNG to at most 256 colors when any of palette, quality, effort or colors is set. That is lossy, so the default PNG settings use none of them and a re-encoded PNG decodes to the same pixels. Opt in for flat graphics, where it saves the most:

js
imageSqueezer({ quality: { png: 80 }, encoderOptions: { png: { palette: true, effort: 10 } } });

quality reaches the PNG encoder only with palette: true, so quality: 70 for every encoder leaves PNG lossless.

Notes ​

  • Files ending in .jpg, .jpeg, .png, .webp, .avif, .gif, .tif and .tiff (in any case) are processed. SVG, every other file and files without contents pass through untouched.

  • Outputs are ordered by width, then by the order of formats. A converted file takes the format's extension (.webp, .avif, .jpg, .png); 'original' keeps the source extension.

  • Images are never enlarged, and names tell the real width: widths larger than the image become one file at the image's own width. A 1000 px image with widths: [640, 1280, 1920] becomes photo-640w.png and photo-1000w.png. The width is the upright one, after the EXIF orientation is applied.

  • keepSmaller applies only to 'original' without widths: the source is emitted unchanged when the re-encoded file is not smaller. Converted and resized files are always the encoder's output.

  • Photos are turned upright from the orientation stored in their metadata before resizing, so widths apply to the upright image.

  • The source's own format is the same as 'original': formats: 'jpeg' on photo.jpeg keeps photo.jpeg (no rename to .jpg) and applies keepSmaller, and formats: ['original', 'png'] on a .png writes the file once.

  • Pages and frames are kept only between formats that can hold them:

    SourceOutputKeeps
    animated GIF or WebPGIF or WebPevery frame
    multi-page TIFFTIFF ('original')every page
    anything elseany other formatthe first page only
  • JPEG has no transparency: transparent pixels become white in JPEG output.

  • An image read as UTF-8 text fails with an error that names the file and says to use src(globs, { encoding: false }).

  • sharp ships prebuilt native binaries per platform as optional dependencies. An install that skips optional dependencies (--no-optional, --omit=optional) leaves them out and sharp fails to load.

Migrating ​

From gulp-imagemin ​

gulp-imagemin, gulp-webp, gulp-sharp-responsiveimage-squeezer
imagemin() with the default pluginsimageSqueezer()
imagemin.mozjpeg({ quality: 75 })quality: { jpeg: 75 } (mozjpeg is on by default)
imagemin.optipng()the default: lossless PNG
imagemin-pngquant({ quality: [0.6, 0.8] })encoderOptions: { png: { palette: true } } and quality: { png: 80 }
imagemin.gifsicle()the default for .gif
imagemin.svgo()not covered: SVG passes through
gulp-webp, gulp-avifformats: 'webp', formats: 'avif'
gulp-sharp-responsive formats: [{ width, format }]formats × widths, named <name>-<width>w.<ext>
gulp-sharp-responsive skipOnEnlargementalways: larger widths become one file at the image's width

From 1.x ​

There was no 1.x: image-squeezer is new in 2.0. After 2.0.0, PNG defaults are lossless (2.0.0 quantized PNGs to a palette through effort: 10), widths larger than the image no longer write files named after a width they do not have, and the source's own format counts as 'original'.

Released under the MIT License.