Skip to content

feat: configure a minimizer or generator in one place, and generate from emitted assets - #727

Merged
alexander-akait merged 13 commits into
mainfrom
feat/generate-presets
Sep 6, 2026
Merged

feat: configure a minimizer or generator in one place, and generate from emitted assets#727
alexander-akait merged 13 commits into
mainfrom
feat/generate-presets

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

Closes the last feature gaps against image-minimizer-webpack-plugin, so it can be deprecated in favour of this plugin (webpack/image-minimizer-webpack-plugin#514 carries the notice and migration guide).

Four things, in order of the commits:

  • Named generators. generate accepts an object of them, and a module picks one with ?as=webp. Naming none leaves the module alone; naming one nothing defines is an error rather than a silent decline.
  • Generating from emitted assets. A generator written as an object can set type: "asset", which writes a new file beside one already emitted — a .webp next to a copied .png — plus filename, filter and deleteOriginalAssets. Unlike "import" generators it does not need an awaitable processResult hook, so it works on released webpack today.
  • [width] / [height] in an asset generator's filename, fed by what the generator reports; sharp already knows. A placeholder nothing reports a size for is an error, not a file called image-[width].webp.
  • Options live with the thing they configure. A minimizer or generator written as an object carries its own options, so it is configured in one place instead of being paired positionally or by name with minimizerOptions / generatorOptions. Both of those keep working and are deprecated, with TODOs to remove them in the next major release; giving both for one minimizer or generator is an error, as is a generatorOptions key naming no generator (which previously ran the generator with no options at all, silently).

One thing deliberately left alone: the examples that configure the default minimizer via minimizerOptions with no minify at all. That option is still the only way to reach the default without naming terserMinify, so the deprecation note says so.

What kind of change does this PR introduce?

feat

Did you add tests for your changes?

Yes — test/generate-option.test.js (presets, asset generation, size placeholders, options resolution and both error cases) and test/minify-option.test.js (a minimizer written as an object, per-entry options in an array, and the both-places error). 594 tests pass.

Does this PR introduce a breaking change?

No. Every existing shape of minify, minimizerOptions, generate and generatorOptions keeps working; the new forms are additive.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

The README is updated in this PR: the new object form for both options, a Migrating from image-minimizer-webpack-plugin section, and the two deprecated options reduced to a note each. Nothing further is needed after merge.

Use of AI

Claude Code wrote the implementation, tests and documentation; I reviewed them before submitting.

🤖 Generated with Claude Code

https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H


Generated by Claude Code

alexander-akait and others added 6 commits September 5, 2026 13:01
`generate` now also accepts an object of named presets. A module asks for
one by name in its query (`./image.jpg?as=webp`); a module naming no preset
is left alone, and an unknown preset is reported as an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
A named generator can now be written as an object carrying `type: "asset"`,
`filename`, `filter` and `deleteOriginalAssets`. An `asset` generator reads
what was emitted rather than a module as it builds, so nothing has to import
its output and it needs no awaitable `processResult` hook.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
`sharp` already knows what it encoded, so it reports the size and the name can
read it. A placeholder no generator reports a size for is an error rather than
a file literally called `[width]`.

Also documents migrating from `image-minimizer-webpack-plugin`, option by
option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
A generator written as an object now carries its own `options`, so one
generator is configured in one place. `generatorOptions` is deprecated but
keeps working, the way `terserOptions` does for `minimizerOptions`.

Two things that used to pass silently are now errors: options given in both
places for one generator, and a `generatorOptions` key naming no generator —
which used to mean the generator ran with no options at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
Every example now gives a generator its options directly, and the option is
covered by one note saying it still works — the same shape `terserOptions`
already has under `minimizerOptions`. TODOs mark it for removal in the next
major release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
A minimizer written as an object carries its own `options`, the way a
generator now does, so one minimizer is configured in one place instead of
being paired positionally with `minimizerOptions`. That option is deprecated
but keeps working, and giving both for one minimizer is an error.

Every example that paired the two is rewritten. The ones configuring the
default minimizer are left as they are: `minimizerOptions` is still the only
way to reach it without naming `terserMinify`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
@changeset-bot

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0e4c7f2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
minimizer-webpack-plugin Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.93088% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.10%. Comparing base (a27c7fd) to head (0e4c7f2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/index.js 93.67% 9 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #727      +/-   ##
==========================================
+ Coverage   94.91%   95.10%   +0.19%     
==========================================
  Files           4        4              
  Lines        1120     1329     +209     
  Branches      391      489      +98     
==========================================
+ Hits         1063     1264     +201     
- Misses         50       57       +7     
- Partials        7        8       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

alexander-akait and others added 7 commits September 5, 2026 21:55
`readPreset` and `generatorFor` decide which generator an asset reaches, and
no build on a released webpack can run them: `generate` in module mode needs a
`processResult` hook that can await, so the integration cases skip themselves.
Driving both directly covers the branches that skip leaves untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
…veral

An `asset` generator that throws, one that returns errors and warnings, and
one whose name resolves to an asset already emitted each take a branch nothing
drove. Two named generators under the filesystem cache cover a salt built from
more than one, which `asset` generators can exercise on any webpack.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
An array may hold the minimizer itself beside an object describing one, and a
described minimizer that names no options still reads the `minimizerOptions`
entry at its index. Nothing drove either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
The last examples still pairing `minify` with `minimizerOptions` now carry
their options inside `minify`, the default minimizer named where it was
implied. Prose that pointed at the option by name points at a minimizer's own
`options` instead, and `normalizeMinimizers` gains the removal TODO its
fallback was missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
An object naming several implementations and pairing them with a list of
options rebuilt the positional coupling the object form exists to remove: two
lists that have to line up, in the one place a reader expects one thing
configured. Several minimizers are an array of objects instead, each carrying
its own options, and a generator names one implementation per preset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
A table of the two side by side — what each reads, what it produces, what
picks it, which fields it reads and which webpack it needs — then a worked
example of each showing the files that come out.

`filename`, `filter` and `deleteOriginalAssets` describe a file written beside
another, so an `import` generator setting one is now an error. They were read
only in `asset` mode, which made a config that looked complete quietly do half
of nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
…med asset

An `"import"` generator renames the asset through
`buildInfo.assetResource`, which every consumer of the asset module reads,
so a `new URL()` and a CSS `url()` follow it just as an `import` does, and
an inlined asset takes the media type of what it became. Only the `import`
path was covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TQeNpahUSDUjD2Crugy5H
@alexander-akait
alexander-akait merged commit a2c490a into main Sep 6, 2026
31 checks passed
@alexander-akait
alexander-akait deleted the feat/generate-presets branch September 6, 2026 11:14
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.

1 participant