npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2026 – Pkg Stats / Ryan Hefner

oi-optimize-images

v3.0.0

Published

πŸ–ΌοΈ Shrink, convert and resize whole folders of images with one short command β€” fast, safe, powered by sharp

Readme

npm version license node

Shrink a whole folder of images with one short command.

oi ./images

Optimized copies land in ./images-oi-out/ β€” your originals are never touched. You get told exactly how many bytes you saved. Powered by sharp.

  oi β€” Optimize Images
  ─────────────────────
  Path:     /home/you/site/images
  Output:   /home/you/site/images-oi-out
  Images:   24
  Quality:  80
  Format:   original (keep)
  Workers:  8

  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100% | 24/24 files

  βœ“ 24 image(s) optimized
  Output: /home/you/site/images-oi-out
  Before: 18.4 MB  β†’  After: 3.1 MB
  Saved: 15.3 MB (83.2%)

✨ Features

  • πŸ“¦ Bulk by default β€” point it at a folder, it walks every subfolder too
  • 🧡 Uses the whole machine β€” encodes one image per core, not one at a time
  • 🎚️ Quality dial β€” one flag, 1 to 100, sensible 80 default
  • πŸ”„ Format conversion β€” JPG, PNG, WebP, AVIF, TIFF, GIF
  • πŸ“ Smart resize β€” fits inside your box and never upscales a small image
  • πŸ“Š Live progress bar β€” plus a before/after savings report at the end
  • πŸ›‘οΈ Non-destructive by default β€” output goes to a sibling -oi-out folder; add --in-place when you want the old overwrite behavior
  • πŸ”’ Safe writes β€” each file lands in a temp file and is renamed, so a cancelled run never leaves a half-written image behind
  • πŸ™ƒ Upright photos β€” bakes in the camera's rotation instead of dropping it
  • 🎞️ Keeps animations β€” animated GIF and WebP keep every frame
  • βš–οΈ Never grows a file β€” if re-encoding would cost bytes, the original size wins instead
  • πŸ’ͺ Keeps going β€” one broken image is reported and skipped, not fatal
  • πŸš€ mozjpeg encoding β€” smaller JPEGs than stock at the same quality
  • πŸͺΆ Tiny install β€” sharp plus cli-progress, nothing else
  • 🧩 Usable as a library β€” require("oi-optimize-images") for the same engine without the CLI

πŸ“₯ Install

Pick your package manager. All four give you the same oi command:

npm install -g oi-optimize-images
yarn global add oi-optimize-images
pnpm add -g oi-optimize-images
bun add -g oi-optimize-images

πŸƒ Or skip installing

npx oi-optimize-images ./images -q 70

πŸš€ Quick start

Squash a folder, keep the formats:

oi ./images

Writes optimized copies to ./images-oi-out/. ./images itself is untouched.

Squash harder:

oi ./images -q 60

Convert everything to WebP:

oi ./images -f webp

Overwrite the originals instead of writing copies:

oi ./images --in-place

Write into a folder you choose:

oi ./images -o ./optimized

Make thumbnails, max 400Γ—400:

oi ./images -f webp -s 400x400 -q 75

Leave some cores free for everything else:

oi ./images -j 2

Just one file:

oi ./pics/photo.jpg

Writes to ./pics-oi-out/photo.jpg β€” a single file follows the same parent-folder sibling rule as a directory.


βš™οΈ Options

| Flag | What it does | Default | |------|--------------|---------| | -q, --quality <1-100> | 🎚️ Output quality | 80 | | -f, --format <fmt> | πŸ”„ original, jpg, png, webp, avif, tiff, gif | original | | -s, --size <WxH> | πŸ“ Fit inside these dimensions, e.g. 800x600 | none | | -j, --concurrency <n> | 🧡 Images encoded at once | one per core | | -o, --output <dir> | πŸ“ Write into <dir> instead of the -oi-out sibling | none | | --in-place | ♻️ Overwrite the source files where they are | off | | -h, --help | πŸ’¬ Show help | |

--in-place and -o can't be combined β€” pick one.

Which quality should I use?

| Value | Good for | |-------|----------| | 90-100 | 🎨 Photography, print, archival | | 75-85 | 🌐 Web images β€” the sweet spot | | 60-75 | ⚑ Thumbnails, previews, speed-first pages | | < 60 | πŸͺΆ When bytes matter far more than looks |


⚑ Speed

Several images encode at once. On an 8-core laptop, 32 photos at 1600Γ—1200 converted to WebP:

| -j 1 | -j 2 | -j 4 | -j 8 (default here) | |--------|--------|--------|--------------------------| | 14.6s | 8.7s | 6.2s | 5.0s |

Same bytes out whichever you pick β€” concurrency only changes how long you wait.

The default reads the cores actually available, so a 2-core VM uses two, and a container capped at one CPU uses one rather than reaching for the host's total. Turning -j down is the useful direction, for when you want the machine back.


⚠️ Good to know

  • Originals stay put by default. Output goes to a sibling folder named <input>-oi-out β€” oi ./images writes into ./images-oi-out, and a single file like oi ./pics/photo.jpg writes into ./pics-oi-out/photo.jpg. Use --in-place to overwrite sources the old way, or -o <dir> to pick the output folder yourself.
  • *-oi-out folders are skipped on the way in. Pointing oi at a parent folder never re-processes a previous run's own output.
  • A file that can't be shrunk still lands in the output folder. Out of place, with the format unchanged, a file that would grow is copied over as is instead of being skipped, so the output stays a complete mirror of the input.
  • -s never enlarges. It uses fit-inside with no upscaling, so an image already smaller than your box is left at its own size.
  • Two files can't share one output. If logo.jpg and logo.png would both become logo.webp, the run stops before writing anything and tells you which two clash. Convert them separately.
  • A file already in the target format is skipped, not fought over. If a folder holds both logo.jpg and logo.webp and you convert it to webp, the .jpg converts and the existing .webp is left alone rather than causing a collision error β€” the same rule that makes a second --in-place run over the same folder safe. The summary says how many were left alone.
  • Re-running costs quality, with --in-place. Each pass re-encodes, so compressing an already compressed file again degrades it further. A rewrite that would come out bigger is thrown away and the original kept β€” but that is a size guard, not a quality one.
  • -j reads the cores you actually have. Inside a container limited to one CPU it uses one worker rather than the host's full count.

Supported inputs: .jpg .jpeg .png .webp .avif .tiff .tif .gif


πŸ“¦ Use it from Node

The CLI is a thin layer over an API you can call yourself:

const { discoverImages, optimizeImages } = require("oi-optimize-images");

const { root, files } = await discoverImages("./images");
const summary = await optimizeImages(files, { inputRoot: root, quality: 70 });

console.log(`Saved ${summary.originalSize - summary.newSize} bytes`);
console.log(`Output in ${summary.outputDir}`);

discoverImages(path) resolves path and returns { root, files }: the folder the run is rooted at and the absolute path of every image found under it (a single file's root is its parent folder). *-oi-out folders are skipped automatically.

By default nothing under root is modified β€” output goes to the ${root}-oi-out sibling. Pass output: "<dir>" for a folder of your choosing, or inPlace: true to overwrite sources instead; inputRoot is required unless inPlace is set. optimizeImages otherwise takes the same tuning options as the flags (quality, format, size: { width, height }, concurrency) and an optional third argument, { onProgress, onFailure }. It never prints and never exits β€” bad input throws a UserError.

summary comes back as { total, optimized, copied, skipped, failed, originalSize, newSize, failures, outputDir }. copied counts files that couldn't be shrunk and were mirrored unchanged; outputDir is null when inPlace was used.

concurrency defaults to one image per core. QUALITY_MIN/QUALITY_MAX and CONCURRENCY_MIN/CONCURRENCY_MAX are exported if you want to validate input before handing it over.


πŸ”€ Migrating from 2.x

  • The default output location changed. oi ./images used to overwrite files in ./images; it now writes into ./images-oi-out and leaves ./images alone. Add --in-place to get the old overwrite behavior back, or -o <dir> to pick a different output folder.
  • -d, --delete-original is gone. Originals are kept by default now, so there's nothing to opt out of deleting. --in-place still overwrites a source in place, including when converting it to a new format.
  • API renames. findImageFiles is now discoverImages and returns { root, files } instead of a plain array. optimizeImages takes inputRoot / output / inPlace instead of deleteOriginal. The summary gained copied and outputDir and lost deleted. optimizeImage and formatBytes are no longer exported.
  • Node >= 20.12 is now required, up from 20.9.0.

🀝 Contributing

bin/oi.js                    shebang launcher, hands off to the CLI
src/
  index.js                   public API β€” the root export
  formats.js                 every supported format: extensions + sharp encoder
  defaults.js                default options, quality and worker ranges
  discover.js                a path in, { root, files } out β€” skips *-oi-out folders
  plan.js                    decides each job's output path and collision rules
  encode.js                  one file: resize, encode, atomic write
  run.js                     many files: plan the jobs, spread over workers, tally a summary
  pool.js                    runs N jobs at a time, results in input order
  errors.js                  problems the user can fix
  cli/
    index.js                 wires parsing, discovery and reporting together
    args.js                  argv -> options
    help.js                  the --help screen
    reporter.js              every line the CLI prints
    tune-runtime.js          sizes the thread pool before sharp loads
test/                        one file per module, plus end-to-end CLI tests

Two rules keep it easy to work in:

  • Formats live in one place. src/formats.js drives --format validation, file discovery, the extension lookup and the sharp call. Supporting a new format is one entry in FORMATS and nothing else.
  • The core never prints and never exits. Anything under src/ outside src/cli/ throws UserError and returns data. src/cli/reporter.js owns the terminal, which is what makes the engine importable and testable.

The suite runs on Node's built-in test runner β€” no framework to install. It generates real images into a temp directory, so it exercises sharp for real rather than mocking it.

npm test

Every user-visible change goes in CHANGELOG.md under [Unreleased], and moves under a version heading at release time.


πŸ“‹ Requirements

Node.js >= 20.12.0

πŸ“„ License

MIT Β© Rubel Hossain