Hi,
we've received Uncaught ReferenceError: Invalid left-hand side expression in postfix operation on version 2.8.0 for all our js projects where we use uglify-js (webpack and browserify bundlers).
Do you have some other reports about this issue?
Yes. It is broken. 2.7.5 still works though.
Same issue here, was able to fix by setting compress: false in my webpack UglifyJsPlugin options.
Please provide a repro.
馃憤 We're using grunt-contrib-uglify which is bringing this in automatically.
We ended up adding uglify 2.7.5 to our package.json, which is then used by webpack, so everything works again.
To fix the issue we'd need a short javascript program that exhibits the error when uglified. Please also provide the uglify options used.
Sucks, we had this issue just now!
It's reproducible on Facebook's invariant.js:
https://github.com/zertosh/invariant/blob/master/invariant.js#L43
argIndex++ is uglified to 0++.
Via Webpack, we have our compress: { warnings: false } in our webpack.optimize.UglifyJsPlugin.
@graulund I just ran uglify-js invariant.js -c warnings=false -b bracketize on that file and got:
"use strict";
var NODE_ENV = process.env.NODE_ENV, invariant = function(condition, format, a, b, c, d, e, f) {
if ("production" !== NODE_ENV && void 0 === format) {
throw new Error("invariant requires an error message argument");
}
if (!condition) {
var error;
if (void 0 === format) {
error = new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");
} else {
var args = [ a, b, c, d, e, f ], argIndex = 0;
error = new Error(format.replace(/%s/g, function() {
return args[argIndex++];
})), error.name = "Invariant Violation";
}
throw error.framesToPop = 1, error;
}
};
I don't use Webpack, but have they specified some other compress options?
A quick fix is to set reduce_vars: false and collapse_vars: false in the compress-object:
compress: {
reduce_vars: false,
collapse_vars: false
}
Relevant commits seem to be 4e49302916fe395f5c63992aa28c33392208fb27 or 16cd5d57a5cf7f5750104df0e5af246708fd493f
Still not reproducible:
$ node_modules/.bin/uglifyjs -V
uglify-js 2.8.0
$ node_modules/.bin/uglifyjs invariant.js -c collapse_vars=false,reduce_vars=false
"use strict";var NODE_ENV=process.env.NODE_ENV,invariant=function(condition,format,a,b,c,d,e,f){if("production"!==NODE_ENV&&void 0===format)throw new Error("invariant requires an error message argument");if(!condition){var error;if(void 0===format)error=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var args=[a,b,c,d,e,f],argIndex=0;error=new Error(format.replace(/%s/g,function(){return args[argIndex++]})),error.name="Invariant Violation"}throw error.framesToPop=1,error}};module.exports=invariant;
$ echo $?
0
$ node_modules/.bin/uglifyjs invariant.js -c collapse_vars=true,reduce_vars=true
"use strict";var NODE_ENV=process.env.NODE_ENV,invariant=function(condition,format,a,b,c,d,e,f){if("production"!==NODE_ENV&&void 0===format)throw new Error("invariant requires an error message argument");if(!condition){var error;if(void 0===format)error=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var args=[a,b,c,d,e,f],argIndex=0;error=new Error(format.replace(/%s/g,function(){return args[argIndex++]})),error.name="Invariant Violation"}throw error.framesToPop=1,error}};module.exports=invariant;
$ echo $?
0
You're right, it's not reproducible for me over CLI either. It must be something in Webpack's UglifyJs wrapper, then?
grunt-contrib-uglify had the same issue WebPack Uglify wrapper had.
You're right, it's not reproducible for me over CLI either. It must be something in Webpack's UglifyJs wrapper, then?
That's a possibility. Some options may not be propagated using the lower level API.
No issue with the uglify minify() API AFAICT:
> require('uglify-js').minify('invariant.js', { compress: { reduce_vars: true, collapse_vars: true } })
{ code: '"use strict";var NODE_ENV=process.env.NODE_ENV,invariant=function(r,e,n,i,o,a,t,s){if("production"!==NODE_ENV&&void 0===e)throw new Error("invariant requires an error message argument");if(!r){var u;if(void 0===e)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var v=[n,i,o,a,t,s],d=0;u=new Error(e.replace(/%s/g,function(){return v[d++]})),u.name="Invariant Violation"}throw u.framesToPop=1,u}};module.exports=invariant;',
map: null }
@graulund Maybe it would be useful to debug against uglifyjs-webpack-plugin if someone can provide a small project. You can control Uglify version against that plugin (it's the same as in webpack core).
I had to fix this immediately for grunt-contrib-uglify which is also broken by this. I made use of https://docs.npmjs.com/cli/shrinkwrap
npm shrinkwrap --devnpm-shrinkwrap.json go search for the explicit version 2.8.0 of uglify-js and change it manually to 2.7.5npm installWe are using grunt-contrib-uglify with only the drop_console-option set and were experiencing the issue with this snippet of code:
function createXMLHTTPObject() {
var XMLHttpFactories = [];
XMLHttpFactories.push(function () {
return new XMLHttpRequest();
});
XMLHttpFactories.push(function () {
return new ActiveXObject("Msxml2.XMLHTTP");
});
XMLHttpFactories.push(function () {
return new ActiveXObject("Msxml3.XMLHTTP");
});
XMLHttpFactories.push(function () {
return new ActiveXObject("Microsoft.XMLHTTP");
});
var xmlHttp = false;
for (var i = 0; i < XMLHttpFactories.length; i++) {
try {
xmlHttp = XMLHttpFactories[i]();
}
catch (e) {
continue;
}
break;
}
return xmlHttp;
}
Running it through grunt-contrib-uglify, we get this:
function b() {
var a = [];
a.push(function () {
return new XMLHttpRequest
}), a.push(function () {
return new ActiveXObject("Msxml2.XMLHTTP")
}), a.push(function () {
return new ActiveXObject("Msxml3.XMLHTTP")
}), a.push(function () {
return new ActiveXObject("Microsoft.XMLHTTP")
});
for (; 0 < a.length; 0++) {
try {
!1 = a[0]()
} catch (b) {
continue
}
break
}
return !1
}
The issue resides in the for-loop, specifically 0++ (at least that's what I distilled...).
Apologies if this is the wrong place to comment and we should rather bug grunt-contrib-uglify, but I figured since you supply the functionality, you might want to be aware :-)
The entry point of moment js is a good sample of the problem
// Moment prototype object
function Moment(config) {
copyConfig(this, config);
this._d = new Date(config._d != null ? config._d.getTime() : NaN);
if (!this.isValid()) {
this._d = new Date(NaN);
}
// Prevent infinite loop in case updateOffset creates new moment
// objects.
if (updateInProgress === false) {
updateInProgress = true;
hooks.updateOffset(this);
updateInProgress = false;
}
}
@baumanno I cannot reproduce this on CLI:
$ uglifyjs test.js -c warnings=false -b bracketize
function createXMLHTTPObject() {
var XMLHttpFactories = [];
XMLHttpFactories.push(function() {
return new XMLHttpRequest();
}), XMLHttpFactories.push(function() {
return new ActiveXObject("Msxml2.XMLHTTP");
}), XMLHttpFactories.push(function() {
return new ActiveXObject("Msxml3.XMLHTTP");
}), XMLHttpFactories.push(function() {
return new ActiveXObject("Microsoft.XMLHTTP");
});
for (var xmlHttp = !1, i = 0; i < XMLHttpFactories.length; i++) {
try {
xmlHttp = XMLHttpFactories[i]();
} catch (e) {
continue;
}
break;
}
return xmlHttp;
}
$ uglifyjs test.js -mc warnings=false -b bracketize
function createXMLHTTPObject() {
var t = [];
t.push(function() {
return new XMLHttpRequest();
}), t.push(function() {
return new ActiveXObject("Msxml2.XMLHTTP");
}), t.push(function() {
return new ActiveXObject("Msxml3.XMLHTTP");
}), t.push(function() {
return new ActiveXObject("Microsoft.XMLHTTP");
});
for (var e = !1, n = 0; n < t.length; n++) {
try {
e = t[n]();
} catch (t) {
continue;
}
break;
}
return e;
}
... where test.js is from your example above.
Would be nice to know what additional options grunt-contrib-uglify supply on top of the default, or whether they have modified the input in some way?
My guess is that it's the new reduce_vars option. Try setting that to false in the compressor options.
@alexlamsl I would advise defaulting that to false until it stabilizes... Can you roll out a new release with this?
We have this error in Create React App end-to-end test suite. I can't give you the exact place where it errors because our logs are not verbose enough, but I suspect the earlier example with invariant is one of those places.
Our options:
compress: {
screw_ie8: true,
warnings: false
},
mangle: {
screw_ie8: true
},
output: {
comments: false,
screw_ie8: true
},
sourceMap: true
We also replace process.env.NODE_ENV with "production" before running Uglify.
Hope this helps somewhat!
systemjs-builder has the same issue as well.
Looking into the code where they minify with Uglify... this is what they use to minify, using the API.
function minify(output, fileName, mangle, uglifyOpts) {
var uglify = require('uglify-js');
var ast;
try{
ast = uglify.parse(output.source, { filename: fileName });
} catch(e){
throw new Error(e);
}
ast.figure_out_scope();
ast = ast.transform(uglify.Compressor(uglifyOpts.compress));
ast.figure_out_scope();
if (mangle !== false)
ast.mangle_names();
var sourceMap;
if (output.sourceMap) {
if (typeof output.sourceMap === 'string')
output.sourceMap = JSON.parse(output.sourceMap);
var sourceMapIn = output.sourceMap;
sourceMap = uglify.SourceMap({
file: fileName,
orig: sourceMapIn
});
if (uglifyOpts.sourceMapIncludeSources && sourceMapIn && Array.isArray(sourceMapIn.sourcesContent)) {
sourceMapIn.sourcesContent.forEach(function(content, idx) {
sourceMap.get().setSourceContent(sourceMapIn.sources[idx], content);
});
}
}
https://github.com/systemjs/builder/blob/f988373a164c9a5c868afc1e57664706d6f2ab79/lib/output.js#L74
This is probably a systemjs-builder issue, just posting it here for reference as how they are using the API.
@mishoo I should probably do that, though I'm still scratching my head as to why I can't reproduce any of these using uglify-js along.
Also, I just wanted to note that we all appreciate your amazing work on Uglify. Mishaps like this happen, but it鈥檚 vitally important that you folks keep innovating to make our builds smaller. 鉂わ笍
If someone got burned by this in production, they should use a lockfile.
@maxired your example doesn't seem to break on CLI either:
$ uglifyjs test.js -c warnings=false -b bracketize
function Moment(config) {
copyConfig(this, config), this._d = new Date(null != config._d ? config._d.getTime() : NaN),
this.isValid() || (this._d = new Date(NaN)), updateInProgress === !1 && (updateInProgress = !0,
hooks.updateOffset(this), updateInProgress = !1);
}
$ uglifyjs test.js -mc warnings=false -b bracketize
function Moment(e) {
copyConfig(this, e), this._d = new Date(null != e._d ? e._d.getTime() : NaN), this.isValid() || (this._d = new Date(NaN)),
updateInProgress === !1 && (updateInProgress = !0, hooks.updateOffset(this), updateInProgress = !1);
}
@alexlamsl indeed, the XMLHttpRequest example works here too. Maybe it happens with some particular set of compressor options..
Edit: I mean in the CLI.
@mishoo @alexlamsl I think both webpack and grunt-contrib-uglify use the low level uglify API, not the uglify minify() API. That may have something to do with it.
gulp-uglify on the other hand uses the minify() API:
https://github.com/terinjokes/gulp-uglify/blob/master/minifier.js
As I don't use any of these uglify wrappers, can anyone confirm whether this [email protected] bug happens with gulp-uglify?
@kzc Good point.
@mishoo shall I roll out 2.8.1 with reduce_vars disabled first before taking the time to investigate this further? Looks like we have enough information in this issue for any post-mortem...
@alexlamsl I would prefer that, since it seems to affect a lot of people and it could take some time until we fully understand the issue. Thank you!
If you need any help testing the fix later, please ping me, and I鈥檒l re-run our e2e tests that caught this against the new version.
Found the same problem and reported it firstly as webpack issue https://github.com/webpack/webpack/issues/4395. Interesting thing is that uglify-js cli works ok, but uglifyjs-webpack-plugin does something extra and final minified files are corrupted.
Reproduceable repository
https://github.com/hinok/webpack-uglify-js-bug
uglify-js 2.8.1 has been released with reduce_vars disabled by default.
Please test and report if there are any further issues.
OK, I can confirm 2.8.1 fixed this for us. Thanks for very quick response!
Can you npm deprecate [email protected] "There is a known issue, please update uglify-js to 2.8.1" to highlight this?
Thanks a lot for the quick fix @alexlamsl
@hinok thanks for the work - I'll look into it now.
@gaearon npm deprecate [email protected] done
It works

Thank a lot for the quick fix, it works! Not sure it could help, but I found that the problem for my uglification was in a for loop:
Running for(var n=t.length;0<n;0++) {/* */} returns:
Uncaught ReferenceError: Invalid left-hand side expression in postfix operation
@rap2hpoutre yep. The 0++ produces that. You can open the console and type 0++; and check the response, but I do not know why.
Thank you for the quick turnaround! We got bitten by way of grunt-contrib-uglify as well, but luckily we had a yarn.lock file that was storing a workable shrinkwrap.
I suspect that [email protected] with reduce_vars=true would work with grunt-contrib-uglify if this patch were applied to the grunt-contrib-uglify project:
diff --git a/tasks/lib/uglify.js b/tasks/lib/uglify.js
index 7a16066..97b74a1 100644
--- a/tasks/lib/uglify.js
+++ b/tasks/lib/uglify.js
@@ -98,7 +98,7 @@ exports.init = function(grunt) {
options.compress.screw_ie8 = false;
}
var compressor = UglifyJS.Compressor(options.compress);
- topLevel = topLevel.transform(compressor);
+ topLevel = compressor.compress(topLevel);
// Need to figure out scope again after source being altered
if (options.expression === false) {
Similarly, uglifyjs-webpack-plugin would likely work with [email protected] with the following patch to the uglifyjs-webpack-plugin project:
diff --git a/src/index.js b/src/index.js
index 9155801..92e6d5c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -87,7 +87,7 @@ class UglifyJsPlugin {
const compress = uglify.Compressor(options.compress || {
warnings: false
}); // eslint-disable-line new-cap
- ast = ast.transform(compress);
+ ast = compress.compress(ast);
}
if(options.mangle !== false) {
ast.figure_out_scope(options.mangle || {});
@bebraw Can you confirm this?
Edit: independently confirmed.
@kzc Ah, I see what could go wrong there. webpack also seems to do that instead of .compress(ast).
This broke us too.
Compressor.compress() was originally introduced into Uglify to implement -c passes=N. It appears that reduce_vars later made use of it.
All upstream uglify-js wrappers should update their code accordingly.
I verified that if bin/uglifyjs were to use the same low level API as webpack and grunt-contrib-uglify then reduce_vars would exhibit the same problem as seen in this thread.
minify() uses Compressor.compress() so it would not experience this problem. Nor would its downstream wrappers such as gulp-uglify.
Ideally all downstream uglify-js wrappers should use the Uglify minify() API rather than the low-level Uglify API.
How Can I update [email protected]. to [email protected]?
Most helpful comment
Also, I just wanted to note that we all appreciate your amazing work on Uglify. Mishaps like this happen, but it鈥檚 vitally important that you folks keep innovating to make our builds smaller. 鉂わ笍
If someone got burned by this in production, they should use a lockfile.