Found by running UglifyJS's test/compress corpus differentially against oxc main: each case is executed with node before and after oxc-minify and the stdout compared. Every item below reproduces on main, verified in plain node. Areas span the minifier, mangler, parser, transformer, and codegen.
delete of a constant-folded NaN changes true to false
console.log(delete (0 / 0));
Original prints true, minified prints false. 0 / 0 is folded to the reference NaN, and delete NaN deletes a non-configurable global. (compressor)
dropping a strict-mode IIFE loses the use strict directive
console.log((() => { "use strict"; return function() { return this; }(); })());
console.log(function(){return this}());
Original prints undefined, minified prints the global object. The dropped arrow carried "use strict", which made the inner plain call's this undefined. (compressor)
for-init no-in restriction leaks into an arrow block body
for (a => { a in a; }; b(););
oxc's parser rejects this with "Expected a semicolon"; node --check and V8 accept it. The for-init [~In] restriction is propagated into the arrow's block body, where in is legal. Arrow expression bodies and function-expression bodies are unaffected. (parser)
BigInt compound exponentiation assignment is mis-lowered
var a = 8n; a **= a; console.log(a);
var a = 8n; a = Math.pow(a, a); console.log(a);
Original prints 16777216n, transformed throws TypeError: Cannot convert a BigInt value to a number. **= is lowered to Math.pow, which rejects BigInt operands. (transformer)
async generator return void 0 defers a finally block
var a = "FAIL";
async function* f(b) { try { if (b) return; else return; } finally { a = "PASS"; } }
f().next();
console.log(a);
var a=`FAIL`;async function*f(e){try{return void 0}finally{a=`PASS`}}f().next(),console.log(a);
Original prints PASS, minified prints FAIL. In an async generator return void 0 awaits its operand before running finally; a bare return does not, so the rewrite reorders the side effect. (compressor)
with-scope substitution of Infinity ignores the with object
var o = { Infinity: "PASS" };
var v = "Infinity";
with (o) { v = Infinity; }
console.log(v);
var o={Infinity:`PASS`},v=`Infinity`;with(o)v=1/0;console.log(v);
Original prints PASS, minified prints Infinity. Infinity is substituted with 1/0, but inside with (o) the name resolves to o.Infinity. (compressor)
mangling collapses catch and let bindings into a TDZ
console.log(function() {
var a = 1;
try { throw ["FAIL", "PASS"]; }
catch ({ [a]: b }) { let a = 0; return b; }
}());
console.log(function(){var e=1;try{throw[`FAIL`,`PASS`]}catch({[e]:e}){return e}}());
Original prints PASS, minified throws ReferenceError: Cannot access 'e' before initialization. The outer a, the catch param b, and the inner let a are all mangled to e, so the computed key reads e in its own TDZ. (mangler)
compressor breaks sloppy arguments-parameter linkage
function f(a) { var a = function() {}; return [arguments, a]; }
console.log(typeof f(42)[0][0]);
function f(e){return[arguments,function(){}]}console.log(typeof f(42)[0][0]);
Original prints function, minified prints number. In sloppy mode arguments[0] is linked to param a; reassigning a updates arguments[0]. Dropping the assignment breaks that link. (compressor)
mangling breaks Annex B block-function hoisting
var x = 42;
{ x(); function x() { console.log("foo"); } }
console.log(typeof x);
var x=42;{e();function e(){console.log(`foo`)}}console.log(typeof x);
Original prints foo then function, minified prints foo then number. Annex B copies the block-scoped function to the outer var x; renaming the block function to e severs that copy. (mangler)
rest-parameter destructuring side effects are dropped
try { (function f(...[ {} ]) {})(); } catch (e) { console.log("PASS"); }
Minified output is empty — the whole IIFE is removed. Original prints PASS, minified prints nothing. Calling f() makes ...[ {} ] destructure undefined, which throws; the IIFE is dropped as dead. (compressor)
optional-chain call loses its detached this binding
(function(o) { console.log((0, o?.f)(42)); })({ a: "PASS", f(b) { return this.a || b; } });
(function(e){console.log((e?.f)(42))})({a:`PASS`,f(e){return this.a||e}});
Original prints 42, minified prints PASS. (0, o?.f)(42) detaches the method so this is not o; dropping the sequence restores the this binding. (compressor)
dropping a unary plus changes coercion timing
console.log(+(A = []) * (A[0] = 1));
console.log((A=[])*(A[0]=1));
Original prints 0, minified prints 1. The + coerces the empty array to 0 before the right operand assigns A[0] = 1; without it the array is coerced after the mutation. (compressor)
DCE drops a throwing call before an explicit throw
function f() {
g();
throw new Error("foo");
if (true) { function g() {} }
}
f();
function f(){throw Error(`foo`)}f();
Original throws TypeError: g is not a function, minified throws Error: foo. g is Annex-B hoisted as undefined (the block function is not yet assigned when throw runs), so g() throws first; DCE removes the call. (compressor)
Found by running UglifyJS's
test/compresscorpus differentially against oxc main: each case is executed withnodebefore and afteroxc-minifyand the stdout compared. Every item below reproduces on main, verified in plainnode. Areas span the minifier, mangler, parser, transformer, and codegen.VOverlay/useActivator.jsrenames a functionlthend)delete of a constant-folded NaN changes true to false
Original prints
true, minified printsfalse.0 / 0is folded to the referenceNaN, anddelete NaNdeletes a non-configurable global. (compressor)dropping a strict-mode IIFE loses the use strict directive
Original prints
undefined, minified prints the global object. The dropped arrow carried"use strict", which made the inner plain call'sthisundefined. (compressor)for-init no-in restriction leaks into an arrow block body
oxc's parser rejects this with "Expected a semicolon";
node --checkand V8 accept it. The for-init[~In]restriction is propagated into the arrow's block body, whereinis legal. Arrow expression bodies and function-expression bodies are unaffected. (parser)BigInt compound exponentiation assignment is mis-lowered
Original prints
16777216n, transformed throwsTypeError: Cannot convert a BigInt value to a number.**=is lowered toMath.pow, which rejects BigInt operands. (transformer)async generator return void 0 defers a finally block
Original prints
PASS, minified printsFAIL. In an async generatorreturn void 0awaits its operand before runningfinally; a barereturndoes not, so the rewrite reorders the side effect. (compressor)with-scope substitution of Infinity ignores the with object
Original prints
PASS, minified printsInfinity.Infinityis substituted with1/0, but insidewith (o)the name resolves too.Infinity. (compressor)mangling collapses catch and let bindings into a TDZ
Original prints
PASS, minified throwsReferenceError: Cannot access 'e' before initialization. The outera, the catch paramb, and the innerlet aare all mangled toe, so the computed key readsein its own TDZ. (mangler)compressor breaks sloppy arguments-parameter linkage
Original prints
function, minified printsnumber. In sloppy modearguments[0]is linked to parama; reassigningaupdatesarguments[0]. Dropping the assignment breaks that link. (compressor)mangling breaks Annex B block-function hoisting
Original prints
foothenfunction, minified printsfoothennumber. Annex B copies the block-scoped function to the outervar x; renaming the block function toesevers that copy. (mangler)rest-parameter destructuring side effects are dropped
Minified output is empty — the whole IIFE is removed. Original prints
PASS, minified prints nothing. Callingf()makes...[ {} ]destructureundefined, which throws; the IIFE is dropped as dead. (compressor)optional-chain call loses its detached this binding
Original prints
42, minified printsPASS.(0, o?.f)(42)detaches the method sothisis noto; dropping the sequence restores thethisbinding. (compressor)dropping a unary plus changes coercion timing
Original prints
0, minified prints1. The+coerces the empty array to0before the right operand assignsA[0] = 1; without it the array is coerced after the mutation. (compressor)DCE drops a throwing call before an explicit throw
Original throws
TypeError: g is not a function, minified throwsError: foo.gis Annex-B hoisted asundefined(the block function is not yet assigned whenthrowruns), sog()throws first; DCE removes the call. (compressor)