@nitra/minify-image
v4.0.0
Published
minify images (PNG, JPEG, SVG)
Readme
Minify images (PNG, JPEG, GIF, SVG)
Minify images in directory, if compressed size lower than 15%
Architecture / system context: see docs/ci4/ (C4 model — context, container, component levels).
Example run
npx @nitra/minify-image --src=.Options
--write If not set, only estimate size difference
--src directory The directory to process.
--avif With --write, create <name>.<ext>.avif (quality 40) next
to each raster image (PNG/JPEG/GIF) before compressing the
original. AVIF generation is skipped inside build/wrapper/cache
directories (dist, build, android, ios, .output, .nuxt, .cache,
src-tauri/icons), and per-package when the nearest package.json
contains `{ "@nitra/minify-image": { "disable-avif": true } }`.
--ignore=<glob> Extra glob to exclude (repeatable). Always-on defaults
(node_modules, vendor, test, dist, src-tauri/icons,
**/.*/**) залишаються активними.
-h, --help Print this usage guide.AVIF companion files
With --write --avif, each raster image (.png/.jpg/.jpeg/.gif) gets a
sibling <name>.<ext>.avif (e.g. hero.png → hero.png.avif) encoded from
the original bytes (quality 40) before the original is compressed in
place. The full source extension is kept in the AVIF filename so two images
that share a basename (hero.png and hero.jpg) do not collide on the same
hero.avif. SVG is skipped (vector → AVIF is pointless).
The AVIF companion is regenerated whenever any of the following holds:
<source>.avifis missing on disk.- The current SHA-1 of
<source>does not match the entry in.n-minify-image.tsv(a previous run processed a different version of the file — i.e. the source has been edited since). - There is no entry for
<source>in.n-minify-image.tsvyet (first run on this file, or upgrade from a cache-less version).
When the source is unchanged and <source>.avif already exists, both files
are left untouched.
.avif companions are not created inside build-output, wrapper, or cache
directories — dist/, build/, android/, ios/, .output/, .nuxt/,
.cache/. Mostly these dirs are already excluded from minification globally;
the --avif filter additionally covers build/, android/, ios/, where
images may exist (Capacitor wrappers, custom build outputs) but native
runtimes do not consume AVIF and the files would be wiped on the next
sync/build anyway. Match is by path segment, so a project named dist-doc/
is not affected.
Per-package opt-out
Workspaces that should not get .avif companions can opt out by adding the
flag to their package.json:
{
"name": "site",
"@nitra/minify-image": {
"disable-avif": true
}
}For each image the CLI walks up the directory tree until it finds the nearest
package.json; if that file has "@nitra/minify-image": { "disable-avif": true },
no .avif companion is generated for that image (the original is still
compressed in place — opt-out applies to AVIF only). The walk stops at the
first package.json found, so a flag on the workspace package.json does not
need to be repeated on the root one.
This mirrors the cleanup contract on the @nitra/cursor
side (rule image-avif): both tools read the same flag, so AVIF generation
and orphan-AVIF cleanup stay in sync. After flipping disable-avif to true,
existing .avif files inside the package have to be removed once by hand —
they will not be regenerated:
find <pkg> -name "*.avif" -deleteTauri / icon assets
Tauri's tauri::generate_context! proc-macro embeds icons from
src-tauri/icons/ into the binary and validates that their PNGs are RGBA —
if any is palette-quantized (8-bit indexed, color-type 3), the build panics
with icon … is not RGBA. To stay safe by construction, **/src-tauri/icons/**
is in the always-on glob ignore list (alongside node_modules, vendor,
test, dist, **/.*/**). Files there are not touched at all — neither the
original PNG nor a sibling .avif is written. This is the canonical Tauri
layout (generated by tauri icon), so the false-positive risk is near zero.
If your icons live somewhere else, exclude that path explicitly with
--ignore=<glob>:
npx @nitra/minify-image --src=. --write --ignore='app-icons/**'Cache
When run with --write, the CLI maintains two TSV files with different
roles and locations:
<src>/.n-minify-image.tsv — committed source of truth
Per line: <relative-path>\t<sha1-hex>\t<originalSize>\t<size>.
This file is the slow-path cache and the source for
Project lifetime savings. Commit it. Lines are sorted alphabetically;
the SHA-1 column changes only when content actually changes — diffs stay
minimal.
After git clone or git checkout (which reset file mtime to checkout
time), the CLI reads each file, computes its SHA-1, compares to the cached
hash; on match the local mtime cache is warmed and no reprocessing happens.
originalSize records the size BEFORE the first compression, fed to
Project lifetime savings: X (Y% across N files) printed at the end of
each --write run.
<src>/node_modules/.cache/@nitra/minify-image/mtime.tsv — local fast path
Per line: <relative-path>\t<mtime>\t<size>.
When (size, mtime) match the cached tuple, the file is skipped without
reading — constant time per file, ideal for the warm dev-loop on a single
machine. Lives under node_modules/ so it is automatically gitignored
(matches the convention used by ESLint, Babel, webpack, Turbo, etc.).
rm -rf node_modules wipes it; the next run rebuilds it via the slow
path against .n-minify-image.tsv — no images are reprocessed.
Migration from versions < 3.2
Earlier versions kept a single <src>/.minify-image-cache.tsv (4 columns:
path\tmtime\toriginalSize\tsize), usually gitignored. On first run after
upgrade:
The new files are created —
<src>/.n-minify-image.tsvis seeded withoriginalSize/sizefrom the old TSV (with empty hash placeholder), soProject lifetime savingsdoes not reset.Each file goes through the slow path (empty hash means cache miss); SHA-1 is computed and stored. No reprocessing happens unless a file's content actually changed.
The old
.minify-image-cache.tsvis left in place — remove it manually:git rm --cached .minify-image-cache.tsv 2>/dev/null || true rm -f .minify-image-cache.tsvAdd
.n-minify-image.tsvto git, ensurenode_modules/covers the local cache (it usually already does).
