Reproducible test case:
./node_modules/.bin/uglifyjs --version
uglify-js 3.0.17
uglify-js version 3.0.16 also displays the same behavior, version 3.0.15 does not.
Use source from react-virtualized: https://www.npmjs.com/package/react-virtualized
./node_modules/.bin/uglifyjs --compress --mangle --output "./react-virtualized.min.js" "./node_modules/react-virtualized/dist/umd/react-virtualized.js"
If you search the input file for "createStyles", you'll see the function definition and the call to it. If you do the same search in the output file, you'll still see the call, but the definition is missing. In uglify-js version 3.0.15, the call and definition are both there, although the function has been renamed.
As a workaround, please try disabling the compress option inline.
From the CLI: uglifyjs -m -c inline=false
The workaround worked. Thanks for the quick response.
@watanas thanks for the report. I shall investigate this in a moment.
repro:
npm install [email protected]
uglifyjs node_modules/react-virtualized/dist/umd/react-virtualized.js -c inline=0 -b bracketize=1 > 0.js
uglifyjs node_modules/react-virtualized/dist/umd/react-virtualized.js -c inline=1 -b bracketize=1 > 1.js
diff -u 0.js 1.js
- var createStyles = function() {
- if (!document.getElementById("detectElementResize")) {
- var css = (animationKeyframes || "") + ".resize-triggers { " + (animationStyle || "") + 'visibility: hidden; opacity: 0; } .resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display:
block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }', head = docu
ment.head || document.getElementsByTagName("head")[0], style = document.createElement("style");
- style.id = "detectElementResize", style.type = "text/css", null != nonce && style.setAttribute("nonce", nonce),
- style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css)),
- head.appendChild(style);
+ var addResizeListener = function(element, fn) {
+ if (attachEvent) {
+ element.attachEvent("onresize", fn);
+ } else {
+ if (!element.__resizeTriggers__) {
+ var elementStyle = _window.getComputedStyle(element);
+ elementStyle && "static" == elementStyle.position && (element.style.position = "relative"),
+ createStyles(), element.__resizeLast__ = {}, element.__resizeListeners__ = [], (element.__resizeTriggers__ = document.createElement("div")).className = "resize-triggers",
+ element.__resizeTriggers__.innerHTML = '<div class="expand-trigger"><div></div></div><div class="contract-trigger"></div>',
+ element.appendChild(element.__resizeTriggers__), resetTriggers(element), element.addEventListener("scroll", scrollListener, !0),
+ animationstartevent && (element.__resizeTriggers__.__animationListener__ = function(e) {
+ e.animationName == animationName && resetTriggers(element);
+ }, element.__resizeTriggers__.addEventListener(animationstartevent, element.__resizeTriggers__.__animationListener__));
+ }
+ element.__resizeListeners__.push(fn);
+ }
+ }, removeResizeListener = function(element, fn) {
+ if (attachEvent) {
+ element.detachEvent("onresize", fn);
+ } else if (element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1),
+ !element.__resizeListeners__.length) {
+ element.removeEventListener("scroll", scrollListener, !0), element.__resizeTriggers__.__animationListener__ && (element.__resizeTriggers__.removeEventListener(animationstartevent, element.__resizeTriggers__.__animationListener__),
+ element.__resizeTriggers__.__animationListener__ = null);
+ try {
+ element.__resizeTriggers__ = !element.removeChild(element.__resizeTriggers__);
+ } catch (e) {}
}
};
return {
- addResizeListener: function(element, fn) {
- if (attachEvent) {
- element.attachEvent("onresize", fn);
- } else {
- if (!element.__resizeTriggers__) {
- var elementStyle = _window.getComputedStyle(element);
- elementStyle && "static" == elementStyle.position && (element.style.position = "relative"),
- createStyles(), element.__resizeLast__ = {}, element.__resizeListeners__ = [], (element.__resizeTriggers__ = document.createElement("div")).className = "resize-triggers",
- element.__resizeTriggers__.innerHTML = '<div class="expand-trigger"><div></div></div><div class="contract-trigger"></div>',
- element.appendChild(element.__resizeTriggers__), resetTriggers(element), element.addEventListener("scroll", scrollListener, !0),
- animationstartevent && (element.__resizeTriggers__.__animationListener__ = function(e) {
- e.animationName == animationName && resetTriggers(element);
- }, element.__resizeTriggers__.addEventListener(animationstartevent, element.__resizeTriggers__.__animationListener__));
- }
- element.__resizeListeners__.push(fn);
- }
- },
- removeResizeListener: function(element, fn) {
- if (attachEvent) {
- element.detachEvent("onresize", fn);
- } else if (element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1),
- !element.__resizeListeners__.length) {
- element.removeEventListener("scroll", scrollListener, !0), element.__resizeTriggers__.__animationListener__ && (element.__resizeTriggers__.removeEventListener(animationstartevent, element.__resizeTriggers__.__animationListener__),
- element.__resizeTriggers__.__animationListener__ = null);
- try {
- element.__resizeTriggers__ = !element.removeChild(element.__resizeTriggers__);
- } catch (e) {}
- }
- }
+ addResizeListener: addResizeListener,
+ removeResizeListener: removeResizeListener
};
$ cat 2105.js
!function(factory) {
factory();
}( function() {
return function(fn) {
fn()().prop();
}( function() {
function bar() {
var quux = function() {
console.log("PASS");
}, foo = function() {
console.log;
quux();
};
return { prop: foo };
}
return bar;
} );
});
$ cat 2105.js | node
PASS
md5-dacc4f73e7d612b6fe5afa0956c3fad8
$ cat 2105.js | bin/uglifyjs -c inline=0 | node
PASS
md5-dacc4f73e7d612b6fe5afa0956c3fad8
$ cat 2105.js | bin/uglifyjs -c inline=1 | node
!void function(){return{prop:function(){console.log,quux()}}}().prop();
^
ReferenceError: quux is not defined
@kzc so unused took out var quux after failing to recognised the use site has moved - not great, but should be fixable. Thanks for narrowing this down.
OT: it scares me how many levels of inline took place in that one example.
Closure in advanced mode figured out prop was used just once and eliminated it:
console.log;
console.log("PASS");
!0;
In our case, quux should not be inlined yet as I haven't taught reduce_vars to scan for variable collision:
var a = 1;
function f() {
return a;
}
console.log(function() {
var a = 2;
return f();
}());
... which means var quux = ... will stick around and prevent the rest to be optimised any further.
@alexlamsl What prevents the following from getting reduced?
$ echo '({ prop: function(){console.log("PASS");} }).prop();' | bin/uglifyjs -c unsafe,passes=3,toplevel
({prop:function(){console.log("PASS")}}).prop();
@kzc obj.prop is done via evaluate:
https://github.com/mishoo/UglifyJS2/blob/b85a358deb70615596bf5ffc668e2ac282453f88/lib/compress.js#L1664
which has the unfortunate side-effect that the resulting value needs to be constant (or RegExp)
You guys rock!