Skip to content

fix: skip terser-webpack-plugin versions that leave bundles unminified - #1444

Open
giaBaoJS wants to merge 2 commits into
callstack:mainfrom
giaBaoJS:fix/terser-plugin-bundle-assets
Open

fix: skip terser-webpack-plugin versions that leave bundles unminified#1444
giaBaoJS wants to merge 2 commits into
callstack:mainfrom
giaBaoJS:fix/terser-plugin-bundle-assets

Conversation

@giaBaoJS

Copy link
Copy Markdown

Summary

  • read the version of the resolved terser-webpack-plugin and only prefer the copy installed in the project root while it can still minify Re.Pack's .bundle assets
  • otherwise fall back to the copy shipped with Re.Pack, and warn about the version that was skipped
  • add getMinimizerConfig unit tests covering the selection

Why

terser-webpack-plugin 5.6.0 added per-minimizer asset filters, and its terser implementation declares filter = (name) => /\.[cm]?js(\?.*)?$/i.test(name). Re.Pack emits index.bundle and [name].chunk.bundle, so every asset is rejected by the filter and dropped before minification runs. Nothing is reported: no error, no warning, and the asset is not flagged [minimized] in stats. Production bundles simply ship unminified.

The report in #1390 attributes this to webpack internals missing under Rspack, but that is not the cause. Reproducing with the exact options Re.Pack passes, on a trivial entry, with only the plugin version changed:

bundler terser-webpack-plugin output
Rspack 1.6.0 5.5.0 210 bytes, minimized
Rspack 1.6.0 5.6.1 1347 bytes, not minimized, 0 errors, 0 warnings
webpack 5.105.4 5.5.0 16 bytes, minimized
webpack 5.105.4 5.6.1 351 bytes, not minimized, 0 errors, 0 warnings

So webpack users are affected too, and the fix should not be scoped to Rspack.

Pinning terser-webpack-plugin to 5.5.0 in packages/repack/package.json covers the fallback branch of getTerserPlugin, but not the root-first branch: a project that resolves 5.6.0 or newer at its own root still gets the silent no-op. That is the common case on pnpm and on hoisted layouts where another dependency pulls in a newer release.

Implementation notes

The version gate is expressed against terser-webpack-plugin, not against a bundler release, so it does not interact with the ongoing Rspack 2 work. When the version cannot be determined the plugin is assumed usable, which keeps the previous behaviour rather than failing a build over an unreadable manifest.

Closes #1390.

Validation

  • pnpm --filter @callstack/repack test: 34 suites, 303 tests passed
  • pnpm test: 10 tasks successful
  • pnpm typecheck, pnpm lint: clean
  • reverting only getMinimizerConfig.ts while keeping the new tests turns the two selection tests red on the assertion (the project's incompatible plugin is chosen), not on an import or compile error
  • end to end against the built package, Rspack, project root holding terser-webpack-plugin@5.6.1: before the change the asset is 1347 bytes and not minimized, after it is 210 bytes and minimized, with the fallback warning printed

terser-webpack-plugin 5.6.0 added per-minimizer asset filters and its terser
implementation only accepts `.js`, `.cjs` and `.mjs` files. Re.Pack emits
`.bundle` files, so every asset is filtered out before minification runs. No
error or warning is reported and production bundles ship unminified. This
affects both Rspack and webpack.

Read the version of the resolved plugin and keep preferring the copy installed
in the project root only while it can still minify Re.Pack's assets. Otherwise
fall back to the copy shipped with Re.Pack and warn about the version that was
skipped.
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

@giaBaoJS is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ce4ccab

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

This PR includes changesets to release 6 packages
Name Type
@callstack/repack Patch
@callstack/repack-plugin-expo-modules Patch
@callstack/repack-plugin-nativewind Patch
@callstack/repack-plugin-reanimated Patch
@callstack/repack-dev-server Patch
@callstack/repack-init Patch

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

Comment thread packages/repack/src/commands/common/config/getMinimizerConfig.ts Outdated
The version check misses two cases. A plugin whose `package.json` is hidden
behind an `exports` map reports no version and gets accepted even though it
filters out `.bundle` assets, and a future release that starts accepting them
would still be rejected because it is newer than 5.6.0.

Load the resolved plugin and ask it directly: `terserMinify.filter` is what the
plugin consults before minifying an asset, so a plugin is usable when it has no
such filter or when the filter does not reject a `.bundle` name. Keep reading
the version for the warning text only, and omit it from the message when it
cannot be read.
@dannyhw

dannyhw commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

I understand the desire to fix this but honestly my personal preference will be to fix the rspack minifier rather than continuing to default to terser

@MikitasK

Copy link
Copy Markdown
Collaborator

I understand the desire to fix this but honestly my personal preference will be to fix the rspack minifier rather than continuing to default to terser

makes sense 👍 since webpack is affected too, wdyt about keeping this fallback for webpack, but using/fixing native minimizer for rspack?

@dannyhw

dannyhw commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

but i believe terser still works for webpack, don't we already prefer the users version for that?

@giaBaoJS

Copy link
Copy Markdown
Author

I measured it before answering, and terser does not still work for webpack. Same root cause as Rspack.

terser-webpack-plugin 5.6.0 added terserMinify.filter = (name) => /\.[cm]?js(\?.*)?$/i.test(name) (dist/utils.js:16 and :310). The webpack asset Re.Pack emits is literally index.bundle (plus *.chunk.bundle), so the filter returns false and the plugin drops the asset from assetsForMinify with no warning and no error.

Real builds in apps/tester-app, webpack 5.105.4, react-native webpack-bundle --platform ios --dev=false, config with no optimization.minimizer so Re.Pack's default runs (which is also the shape of templates/webpack.config.mjs):

terser-webpack-plugin in the project main.jsbundle
5.5.0 1,969,176 bytes, mangled
5.6.1 4,846,598 bytes, `/******/ var self = self

And preferring the user's version is what triggers this rather than what protects against it. webpack itself depends on terser-webpack-plugin: ^5.3.17, so a plain npm i webpack@5.105.4 puts 5.6.1 at the project root today, and resolveTerserPluginCandidate([rootDir]) looks there first. Under pnpm nothing is hoisted, Re.Pack's own pinned 5.5.0 wins, and it works. An rspack-only npm project has no terser at the root at all, so webpack is the more exposed of the two.

Two things you may want before deciding on this PR:

  1. I forced SwcJsMinimizerRspackPlugin on Rspack 1.6.0 with Re.Pack's test regex and it minified fine: 1,972,343 bytes in 2.51 s vs terser's 1,969,836 in 6.37 s, async chunk minified too. The comment in getMinimizerConfig.ts still points at the 1.5.0 regression from fix: use Terser for Rspack 1.5.0 and above temporarily #1273. That may already be unblocked, which would be your preferred direction. I did not run the app, so this is "produces minified output", not "verified at runtime".

  2. There is a smaller fix than what this PR currently does. A plain function has no .filter property, so any version accepts the asset:

new TerserPlugin({
  test: /\.(js)?bundle(\?.*)?$/i,
  extractComments: false,
  minify: function repackTerserMinify(input, sourceMap, minimizerOptions, extractComments) {
    return require('terser-webpack-plugin').terserMinify(
      input, sourceMap, minimizerOptions, extractComments
    );
  },
  terserOptions: { format: { comments: false } },
})

I ran that with 5.6.1 in both the project and Re.Pack's own node_modules, so the default terser was a confirmed no-op and only the wrapper could do work: 1,969,176 bytes, byte identical to the 5.5.0 output. terserMinify is exported by 5.5.0 as well, and the wrapper is self contained so it survives worker serialization. It drops the capability probe, the fallback, and the warning.

Happy to rewrite #1444 as that wrapper, or to close it if you would rather fix the Rspack minimizer first and handle webpack separately.

@dannyhw

dannyhw commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

I see, seems like i may have misunderstood the issue then. What do you propose as the right solution? I.e do you think what you mentioned in your comment (wrapper) is better or the solution presented in the pr already?

@giaBaoJS

Copy link
Copy Markdown
Author

The wrapper, and I would rather rewrite this PR as that than merge what is here now.

What is here reacts to the symptom. It probes whether the installed plugin will refuse .bundle, then falls back to Re.Pack's own pinned copy and warns. It works, but it needs a capability probe, a fallback path and a warning string to do it, and the user ends up minifying with a different terser than the one in their lockfile.

The wrapper removes the condition instead. In 5.6.1 the dispatch is index.js:357:

if (typeof impl.filter !== "function" || ... impl.filter(name, info) !== false) {

so the filter is only consulted when the configured minify has one. Every built-in gets .filter assigned in utils.js, but a plain function does not, so passing our own named function makes every version from 5.5.0 up minify the asset, and the version that runs is the user's own. That is what "we prefer the user's version" was supposed to give them in the first place.

The judgement call worth saying out loud: we would be deliberately stepping around a filter the plugin author added. I think it is defensible here, since the doc comment on that option describes it as "return true when the minimizer supports the asset" and terser genuinely does support this asset. .bundle is JavaScript, and JS_FILE_RE is an extension heuristic rather than a capability test. But it is a call, not a fact, so it is yours to make rather than mine.

Say the word and I will rewrite it. If you would rather land the Rspack minimizer switch first and treat webpack on its own, I am equally happy to close this and open the wrapper as a separate small PR whenever it suits you.

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.

Default minimizer silently no-ops with terser-webpack-plugin >= 5.6.0 under Rspack — production bundles ship unminified

3 participants