You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Apr 4, 2025. It is now read-only.
Repository navigation
This repository was archived by the owner on Apr 4, 2025. It is now read-only.
build-optimizer: "logical OR" property assignment is removed #388
Without the build optimizer (using -prod --aot --build-optimizer=false)
The highlighted code snippet here (picture from the ESM 5 file button.js file) is being removed when running with the build optimizer. If you add a console.log before, the console.log stays, just the specific code snippet is being removed.
Switching to a ternary, or simple if-else will fix the issue.
I'm looking into this problem and I think the current behaviour, although not very intuitive, is correct.
This is a situation similar to #364, of which the most important bit is:
This is expected behaviour since Build Optimizer consider all code in @angular/* packages to be side-effect free. This causes all top level function calls and constructor to get the /*@__PURE__*/ comment, which will later cause those statements to be dropped if their values are not used.
The less obvious effect of this is that UglifyJS (with pure_getters=true, which we use) will later completely remove (button || anchor).color = DEFAULT_ROUND_BUTTON_COLOR;.
However, looking a bit more into what being side effect free means, this makes sense.
Consider this snippet:
// test.js
var MatFab = /*@__PURE__*/ (function () {
function MatFab(button, anchor) {
(button || anchor).color = 'accent';
}
return MatFab;
}());
var button = {};
MatFab(button);
console.log(button);
One would expect it to print {color: "accent"}.
But if you run it through uglify (uglifyjs -c toplevel,pure_getters=true -b -- test.js) you will get:
And here you see it does not set the property anymore.
We say MatFab is side effect free through the /*@__PURE__*/ comment. This means that, when its return value is not used, the statement can safely be dropped because it did not have any side effects. But modifying a property of an argument is a side effect, so MatFab is not actually side effect free.
This is the second problema related to side effects we've had in the last week and I think there will only be more in the future unless an automated way to detect these is implemented. I'm not sure that's even possible, but maybe some linting rules (e.g. don't mutate arguments or call void functions) will go a long way.
I think running unit tests on purified code is really smart and should work. We can setup our own pipeline for that, but it makes me think tooling should be able to do this sort of thing in the future.
I think it's possible to detect these things, and UglifyJS does it, but in this case we specifically set the pure_getters=true option. It means that property access is always side effect free. If you take it out then the statement is not removed anymore. But this property is responsible for a big part of our size savings.
I don't understand several things. Sorry for stupid questions:
(button || anchor).color = DEFAULT_ROUND_BUTTON_COLOR; is a setter so why does pure_getters=true matter? are we sure that the uglify option is responsible for the assignment getting DCE-ed?
what does this code look like in it's original form? can you link to a snippet from the material repo?
It might also be the interaction of pure_getters with a default option though.
It's also relevant to notice that changing (button || anchor).color = 'accent'; to just button.color = 'accent'; will not cause the statement to be dropped even with pure_getters=true. I don't really understand why this last bit changes behaviour.
If I had to bet, the expression inside parentheses causes UglifyJS to consider the left hand expression as a getter instead of a setter. This might be a UglifyJS bug/limitation (/cc @kzc).
But even if that's the case, having a function that alters one of the arguments and returns nothing seems like a side effect to me. Retaining the function call would be a limitation of side-effect detection in UglifyJS.
The bug should still be fixed because it's unintended behaviour for the option though.
Awesome! That makes so much more sense because I kept on looking at the code and wondering why the assignment was getting stripped. Sanity restored.
I do have to say that this code, particularly the constructor with non-local side-effect, is very odd looking and while it doesn't break our optimizations it should raise some eyebrows.
Activity
fix(button): accent color not set on fab buttons with build optimizer
fix(button): accent color not set on fab buttons with build optimizer
filipesilva commentedon Jan 15, 2018
I'm looking into this problem and I think the current behaviour, although not very intuitive, is correct.
This is a situation similar to #364, of which the most important bit is:
In this case, the original code
will be modified to
A couple of things are happening here:
decoratorsandctorParametersare removed as per the scrub-file transformThe less obvious effect of this is that UglifyJS (with
pure_getters=true, which we use) will later completely remove(button || anchor).color = DEFAULT_ROUND_BUTTON_COLOR;.However, looking a bit more into what being side effect free means, this makes sense.
Consider this snippet:
One would expect it to print
{color: "accent"}.But if you run it through uglify (
uglifyjs -c toplevel,pure_getters=true -b -- test.js) you will get:And here you see it does not set the property anymore.
We say
MatFabis side effect free through the/*@__PURE__*/comment. This means that, when its return value is not used, the statement can safely be dropped because it did not have any side effects. But modifying a property of an argument is a side effect, soMatFabis not actually side effect free.This is the second problema related to side effects we've had in the last week and I think there will only be more in the future unless an automated way to detect these is implemented. I'm not sure that's even possible, but maybe some linting rules (e.g. don't mutate arguments or call void functions) will go a long way.
/cc @IgorMinar @petebacondarwin
petebacondarwin commentedon Jan 15, 2018
Really this should be catchable by running the unit tests for the library code on the "purified" code, right?
For the future (in this case), would it be possible to parse the code and look for mutations to passed in parameters; and so not mark as PURE?
filipesilva commentedon Jan 15, 2018
I think running unit tests on purified code is really smart and should work. We can setup our own pipeline for that, but it makes me think tooling should be able to do this sort of thing in the future.
I think it's possible to detect these things, and UglifyJS does it, but in this case we specifically set the
pure_getters=trueoption. It means that property access is always side effect free. If you take it out then the statement is not removed anymore. But this property is responsible for a big part of our size savings.IgorMinar commentedon Jan 19, 2018
I don't understand several things. Sorry for stupid questions:
(button || anchor).color = DEFAULT_ROUND_BUTTON_COLOR;is a setter so why doespure_getters=truematter? are we sure that the uglify option is responsible for the assignment getting DCE-ed?fix(button): accent color not set on fab buttons with build optimizer
filipesilva commentedon Jan 22, 2018
@IgorMinar this is how the original code looks like: https://github.com/angular/material2/blob/45235566588805ebb06f59f9b7826b865429ec3e/src/lib/button/button.ts#L77-L83
@devversion has a PR to change this logic (angular/components#9376).
Regarding
pure_gettersbeing responsible, I tested using the same test input:And varying the
pure_getters=trueoption:uglifyjs -c toplevel,pure_getters=true -b -- test.js:uglifyjs -c toplevel -b -- test.js:It might also be the interaction of
pure_getterswith a default option though.It's also relevant to notice that changing
(button || anchor).color = 'accent';to justbutton.color = 'accent';will not cause the statement to be dropped even withpure_getters=true. I don't really understand why this last bit changes behaviour.If I had to bet, the expression inside parentheses causes UglifyJS to consider the left hand expression as a getter instead of a setter. This might be a UglifyJS bug/limitation (/cc @kzc).
But even if that's the case, having a function that alters one of the arguments and returns nothing seems like a side effect to me. Retaining the function call would be a limitation of side-effect detection in UglifyJS.
The bug should still be fixed because it's unintended behaviour for the option though.
kzc commentedon Jan 22, 2018
@filipesilva Please report this to the uglify project as an ES5 bug.
Keep in mind that
pure_gettersis a non-default option.kzc commentedon Jan 22, 2018
It appears to be a regression from
uglify-js@3.2.2. The commit that introduced the issue is mishoo/UglifyJS@0b0eac1filipesilva commentedon Jan 22, 2018
@kzc opened as mishoo/UglifyJS#2838 👍
filipesilva commentedon Jan 23, 2018
The UglifyJS bug bit of this issue was fixed in mishoo/UglifyJS#2839 and should be out on the next release.
fix(button): accent color not set on fab buttons with build optimizer (…
IgorMinar commentedon Jan 24, 2018
Awesome! That makes so much more sense because I kept on looking at the code and wondering why the assignment was getting stripped. Sanity restored.
I do have to say that this code, particularly the constructor with non-local side-effect, is very odd looking and while it doesn't break our optimizations it should raise some eyebrows.
fix(button): accent color not set on fab buttons with build optimizer (…
filipesilva commentedon Jan 30, 2018
Closing as the devkit issue is sorted, as far as I can tell.