#1724·terser

5.51.0 regression: a called function is replaced with an empty arrow

Author: bressler95topsCreated Aug 26, 2026Updated Aug 27, 2026

5.51.0 regression: a called function is replaced with an empty arrow (()=>{}), yielding undefined at runtime

Hello, we are using invenioRDM which has terser as a depedency and we we ran into some issues with our js rendering and the below information is what we believe to be the culprit upstream in your recent 5.51 push. We are planning to pin our application 5.50 for now but we thought you should know about it.

Summary

In 5.51.0, compress can replace the callee of a call expression with an empty arrow function (()=>{}), while leaving the original function declaration intact elsewhere in the output. The call then returns undefined instead of its real value, and the program breaks at runtime.

This is a regression: 5.50.0 compiles the same input correctly.

Versions

Broken 5.51.0 (2026-08-26)
Last good 5.50.0 (2026-08-11)
Node v18.20.8

Regression source

Bisected to 18db852c94c302bfe16aa310f4f1adc18f87c8b7"pin (deoptimize) functions that use argnames scope in an ambiguous way. Closes #1478" (lib/ast.js +20/-3, lib/scope.js +45/-0).

Running the reproduction below against lib/ at each ref (minify(src, { compress: {}, mangle: false })):

ref corrupted sites output bytes
v5.49.2 0 705,868
v5.50.0 0 705,868
9fc8a85aa — fix nullish coalescing… 0 705,868
18db852c9 — pin (deoptimize) argnames scope… 7 869,644
v5.51.0 7 869,644

Only two functional commits landed between 5.50.0 and 5.51.0, and the regression is isolated to the second. This is consistent with the rest of the report: the deoptimization explains the ~23% size increase, and reduce_vars/unused being the suppressing options (below) points at the same machinery.

Reproduction

The input is a published, unmodified npm artifact — [email protected], file themes/silver/theme.js:

bash
npm pack [email protected]
tar xzf tinymce-6.8.6.tgz

npx [email protected] package/themes/silver/theme.js -c > out-5.50.0.js
npx [email protected] package/themes/silver/theme.js -c > out-5.51.0.js

# count call sites whose callee became an empty arrow
grep -o '(()=>{})(' out-5.50.0.js | wc -l   # 0
grep -o '(()=>{})(' out-5.51.0.js | wc -l   # 7

Note this is stock -c (compress defaults, no mangle) — no unusual options are required.

Output also grows substantially, which seems related: 705,888 → 869,670 bytes (+23%).

Actual vs. expected

Example 1 — theme.js:483-484

Source:

javascript
const blank = r => s => s.replace(r, '');
const trim$1 = blank(/^\s+|\s+$/g);

5.50.0 (correct — inlines blank into the call):

javascript
trim$1=(r=>s=>s.replace(r,""))(/^\s+|\s+$/g)

5.51.0 (wrong — keeps blank, but calls an empty arrow):

javascript
blank=r=>s=>s.replace(r,""),
trim$1=(()=>{})(/^\s+|\s+$/g)

trim$1 is now undefined rather than a function.

Example 2 — theme.js:2289 and theme.js:2418

Source:

javascript
const can = (name, predicate) => {
  return { key: name, value: nu$9({ can: predicate }) };
};
// ...
const events$i = derive$2([can(focus$4(), (component, simulatedEvent) => { /* ... */ })]);

5.50.0 (correct):

javascript
events$i=derive$2([((name,predicate)=>({key:name,value:nu$9({can:predicate})}))(focus$4(),...)])

5.51.0 (wrong):

javascript
events$i=derive$2([(()=>{})(focus$4(),(component,simulatedEvent)=>{...})])

This example is the clearest evidence that the substitution is simply incorrect rather than a legitimate optimization: can is referenced 18 times, and 5.51.0 still emits its correct definition

javascript
can=(name,predicate)=>({key:name,...

— while substituting an empty arrow at this one call site. The reference appears to resolve to the wrong value.

Runtime impact, and why it was hard to trace

derive$2 maps over its argument array reading .key off each element. Because can(...) now returns undefined, the array is [undefined] and evaluation throws at module load:

Uncaught TypeError: Cannot read properties of undefined (reading 'key')

Some context on how this reached us, since it may be useful for judging severity.

We hit this in an InvenioRDM application — a Flask/React digital repository platform. Its asset pipeline is webpack 5.105.3 with [email protected], which declares terser: ^5.31.1. Our application dependencies are version-pinned, but that range is transitive and our build has no committed lockfile, so 5.51.0 was picked up automatically on the day it was published and the next image build shipped broken JavaScript. The build itself succeeded with no warnings.

The damage was much wider than the one bad file, because Invenio's webpack config uses splitChunks: { chunks: "all" }. That hoisted TinyMCE into a shared vendor chunk alongside unrelated libraries:

shared chunk — 113 modules
  47  lodash
  27  lodash-es
  24  tinymce            <- the miscompiled module
   5  @tinymce
   1  formik
   1  react-invenio-forms
   …

The TinyMCE module throws while that chunk is initializing, so webpack abandons the rest of the chunk's startup. Every other module in it is left partially initialized. Downstream consumers then fail with errors like:

Uncaught TypeError: (0 , m.showHideOverridable) is not a function

…where showHideOverridable is a perfectly valid export of react-invenio-forms that simply never got assigned. So the visible symptom was blank pages and missing-export errors in React components with no connection to TinyMCE, several layers removed from the actual defect. Tracking it back to a minifier took a while precisely because nothing pointed at the build tool.

Which options are involved

Counts of corrupted call sites in the file above, terser 5.51.0:

compress options corrupted sites
true (defaults) 7
{ inline: 2 } 7
{ inline: 1 } 10
{ inline: false } 10
{ ecma: 5 } 7
{ inline: 2, reduce_vars: false } 0
{ inline: 2, unused: false } 0

Disabling either reduce_vars or unused suppresses it entirely. inline is not the trigger — turning it off produces more corrupted sites, not fewer.

Scope

I minified every third-party source file in our bundle with both versions and compared output byte-for-byte — 1,875 files, complete coverage of the bundled node_modules set:

  • 1,873 produced byte-identical output between 5.50.0 and 5.51.0.
  • 1 differed only in mangled-name allocation (video.js — semantically identical, cosmetic).
  • 1 was miscompiled: tinymce/themes/silver/theme.js.

So 5.51.0 changes almost nothing relative to 5.50.0, but what it does change is wrong. I also checked [email protected] (current) and it is unaffected, so the trigger appears to be a code pattern present in the 6.x theme.

I tried to reduce this to a small standalone case and could not — truncated prefixes of the file compile correctly, so the trigger appears to depend on surrounding scope, which is consistent with the scope-pinning change identified above. Happy to test any patch against the full file.