Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark vars with /** @const */ pragma as consts so they can be eliminated. #928

Merged
merged 5 commits into from
Jan 20, 2016

Conversation

STRML
Copy link
Contributor

@STRML STRML commented Jan 19, 2016

Fixes older browser support for consts and allows more flexibility
in dead code removal.

This means that this:

/** @const */ var __DEV__ = false; // could be a NODE_ENV check with envify
if (__DEV__) {
  do_something_expensive();
}

will compile to:

var __DEV__ = false;

successfully removing the dead code without requiring the const keyword, which is not supported in many browsers.

This was born from nodejs/node#3104 (comment), essentially mimicking Closure Compiler's behavior on this. This annotation will allow us to keep the bundle ES5-compliant, pre-compute the environment (to fix Node's slow process.env performance), and successfully eliminate dead code in browser builds both built by the package maintainers and those consumed by webpack/browserify users who are using this version of UglifyJS2.

Please let me know if I'm not on the right track with this PR - I am not very familiar with this project.

Also fixes #133.

@avdg
Copy link
Contributor

avdg commented Jan 19, 2016

I wonder if this should be documentated.

@STRML
Copy link
Contributor Author

STRML commented Jan 19, 2016

Aware of the failing test, will fix - agreed that there should be a README update as well.

Fixes older browser support for consts and allows more flexibility
in dead code removal.
@kzc
Copy link
Contributor

kzc commented Jan 19, 2016

Looks like a good feature. Being compatible with Closure is a plus.

Out of curiosity, are you aware of the uglifyjs --define flag?

$ echo "console.log(DEV,DEV?'dev':'prod');" | uglifyjs --define DEV=true -c warnings=0 
console.log(!0,"dev");

$ echo "console.log(DEV,DEV?'dev':'prod');" | uglifyjs --define DEV=false -c warnings=0 
console.log(!1,"prod");

@STRML
Copy link
Contributor Author

STRML commented Jan 19, 2016

@kzc Yes - the intention here is to create constants that may be expensive to compute (e.g. based on process.env) that don't require the library consumer to know which constants to define when minifying.

See this comment for an example.

@kzc
Copy link
Contributor

kzc commented Jan 19, 2016

So you're depending on envify or some other compile-time transpiler to turn process.env.NODE_ENV into a constant in order for the "const" var elimination to work?

@rvanvelzen
Copy link
Collaborator

I like the idea. Though, the implementation doesn't work - you're looking at the first statement of it's current scope, which isn't correct at all.

I'm not really sure how to work around that right now, as the available tokens don't have the comments in them as far as I can see.

@STRML
Copy link
Contributor Author

STRML commented Jan 20, 2016

@rvanvelzen In what circumstances will it break? It's working fine in the test, and after toying with some additional test cases, it seems to work identically to const.

@rvanvelzen
Copy link
Collaborator

If you add a var declaration above the one with the annotation, it won't work.

@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

and it will produce the wrong result in other cases:

without uglify:

$ echo "/**@const*/var c=3; var d=4; function f() {console.log(c+d);} d+=7; f();" |node
14

with uglify with your patch:

$ echo "/**@const*/var c=3; var d=4; function f() {console.log(c+d);} d+=7; f();" | bin/uglifyjs -c |node
7

Your patch is incorrectly applying "const" behavior to all vars in the scope.

@STRML
Copy link
Contributor Author

STRML commented Jan 20, 2016

Understood. Still new to this project. I found some documentation, I'll go through it and fix the issue. Thanks.

@STRML
Copy link
Contributor Author

STRML commented Jan 20, 2016

I think I found the right solution. Please review - I also made the tests much more complex to catch scope issues.

@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

Should tighten up the regex for the pragma:

$ echo "/* this is not a @constraint */var c=3; c && console.log('hello');" | bin/uglifyjs -c
var c=3;console.log("hello");

@STRML
Copy link
Contributor Author

STRML commented Jan 20, 2016

Thanks for the note @kzc, adding a word boundary to the end of it catches:

/** @const */
// @const
/*@const*/
/**@const*/

but not:

// @constraint
/** @constraint */

etc.

@rvanvelzen
Copy link
Collaborator

This looks really good, nice job! I'm not completely sold on the variable, but I don't really see a viable different solution.

rvanvelzen added a commit that referenced this pull request Jan 20, 2016
Mark vars with /** @const */ pragma as consts so they can be eliminated.
@rvanvelzen rvanvelzen merged commit b5a7197 into mishoo:master Jan 20, 2016
@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

What does Closure accept as a valid pragma?

/**@const*/ var a = 1;

/*@const*/ var b = 2;

//@const
var c = 3;

All of the above?

Their docs show all their pragmas starting after /**

@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

@rvanvelzen In case there's an unanticipated issue shouldn't this be behind a new compress flag defaulting to disabled? -c pragma=true or something.

@rvanvelzen
Copy link
Collaborator

Hmm. I don't believe this annotation to be a common thing in the wild. If it is, we can add the option later.

However, even if we add an option, I'd like to default it to true. These options are highly undiscoverable by nature, and I'd like everyone to benefit by default whenever possible.

@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

Since there will likely be other pragmas in the future, having a compress pragma option defaulting to true would be reasonable. At least then you have the option of explicitly disabling them.

@STRML
Copy link
Contributor Author

STRML commented Jan 20, 2016

Aside from with or eval, is there anything blocking us from making this optimization all the time if we detect a var hasn't been reassigned?

@kzc
Copy link
Contributor

kzc commented Jan 20, 2016

That's why I made the envify comment above.

If the const variable declaration is not constant, its instances cannot currently be replaced.

@kzc
Copy link
Contributor

kzc commented Jan 21, 2016

I misread the question. I don't see a reason why the same const optimization can't be made on vars that haven't been modified. For that matter, if a const variable has not been modified (as one would expect in use strict mode), I don't see why it can't be automatically converted to a var for additional character savings and to chain with adjacent var definitions. Google Closure does this.

@avdg avdg mentioned this pull request Feb 5, 2016
@alexlamsl alexlamsl mentioned this pull request Feb 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Server rendering is slower with npm react const breaks IE?
4 participants