Skip to content

Build/Test Tools: Replace grunt-contrib-imagemin with maintained optimizers - #13562

Open
manzoorwanijk wants to merge 7 commits into
WordPress:trunkfrom
manzoorwanijk:build/replace-imagemin-toolchain
Open

manzoorwanijk wants to merge 7 commits into
WordPress:trunkfrom
manzoorwanijk:build/replace-imagemin-toolchain

Conversation

@manzoorwanijk

@manzoorwanijk manzoorwanijk commented Sep 16, 2026

Copy link
Copy Markdown
Member

grunt-contrib-imagemin was last released in March 2020, and the gifsicle, jpegtran-bin and optipng-bin versions it pins were all published in late 2018. Their macOS builds are x86_64 only, so grunt precommit:image fails on Apple Silicon unless Rosetta is installed. This replaces the plugin with a local task built on optimizers that are still maintained.

  • The imagemin:core task in Gruntfile.js is now local and uses @napi-rs/image (oxipng for PNG, MozJPEG for JPEG) and @343dev/gifsicle, both shipping prebuilt binaries for every supported platform.
  • The output stays lossless, and images that come back larger are left untouched.
  • The dependency tree loses 330 packages and gains 3.
  • The last commit re-runs the task: 183 of the 284 images in scope shrink by 301,223 bytes, each decoding to identical pixels.

Alternatives considered

  • Upgrading: there is no newer grunt-contrib-imagemin, and the newest gifsicle, jpegtran-bin and optipng-bin releases are from May 2022.
  • Overrides: the plugin is CommonJS and pinned to imagemin 6, so forcing imagemin 7 or later breaks it.
  • System binaries from PATH: no dependencies to maintain, but every contributor installs them separately and nothing is pinned.
  • A CI check in place of the task: it would not cover patches uploaded to Trac.

Trac ticket: https://core.trac.wordpress.org/ticket/66118

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code, Codex
Model(s): Claude Opus 5
Used for: Comparing the replacements, writing the Grunt task, and verifying that every changed image decodes to identical pixels. Reviewed and edited by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

…timizers.

`grunt-contrib-imagemin` was last released in March 2020, and it pins older majors of the packages that provide its binaries: the `gifsicle`, `jpegtran-bin` and `optipng-bin` versions it installs were all published in late 2018. Their macOS builds are x86_64 only, so `grunt precommit:image` fails on Apple Silicon unless Rosetta is installed.

`imagemin:core` is now a local multi task built on `@napi-rs/image` (oxipng for PNG, MozJPEG for JPEG) and `@343dev/gifsicle`, which are actively released and ship prebuilt binaries for every supported platform. Images are optimized several at a time, and GIFs are left alone on the few platforms that have no prebuilt gifsicle binary.

The results stay lossless. PNG keeps its exact pixels, JPEG is rewritten as progressive with optimized Huffman tables, and GIF is optimized with `gifsicle -O3 --interlace`. Images that come back larger are left untouched, which the previous task did not do.

The dependency tree loses 330 packages and gains 3.
…images.

The new optimizers find savings the previous ones left behind: 183 of the 284 images in scope shrink by 301,223 bytes in total, about 3 percent.

Every changed file decodes to identical pixels. Some PNGs also drop from RGBA to grayscale with alpha, which is a lossless change of the stored color type.
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@manzoorwanijk
manzoorwanijk marked this pull request as ready for review September 16, 2026 14:11
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props manzoorwanijk, lucatume.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@lucatume lucatume left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A refactor for the sake of maintainability.

I've not visually checked all the modified images, they are byte different, but pixel identical.
The odd one is src/wp-includes/images/smilies/icon_redface.gif that is not pixel identical, but the different pixels have alpha 0 (transparent).
Could something be done here (I'm thinking knobs in the imagemin task, to avoid changes to images that will not actually change?

Comment thread Gruntfile.js Outdated
…in`.

The task is about 140 lines, which is more than belongs inline in the Gruntfile. It now lives in `tools/imagemin/task.js` and is registered with a single line.

It also takes a `minSavedBytes` option, defaulting to 64. Optimizers keep finding a handful of bytes in images that are already optimal, and rewriting a versioned binary file for that is not worth it. With the option in place the task also settles in a single pass, where one PNG previously needed a second.
…imum.

Sixty images saved between one and sixty-three bytes, so they are no longer rewritten. What remains is 123 images and 299,901 bytes, which is 99.6 percent of the savings from before.
@manzoorwanijk

Copy link
Copy Markdown
Member Author

I've not visually checked all the modified images, they are byte different, but pixel identical. The odd one is src/wp-includes/images/smilies/icon_redface.gif that is not pixel identical, but the different pixels have alpha 0 (transparent). Could something be done here (I'm thinking knobs in the imagemin task, to avoid changes to images that will not actually change?

That one is a palette reorder worth 4 bytes, so it is skipped now: the task takes a minSavedBytes option, default 64, which drops it and 59 other images (7032586, 42363ef).

@lucatume lucatume left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking.
I've ran the task locally and verified the images using AI and all checks out.
From the mechanical point of view, this looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants