-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Support marking a call as pure #1448
Conversation
lib/compress.js
Outdated
@@ -1273,7 +1273,21 @@ merge(Compressor.prototype, { | |||
def(AST_Constant, return_false); | |||
def(AST_This, return_false); | |||
|
|||
var pure_regex = /[@#]pure/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The devil in me thinks https://docs.read.me/notes#pure_in_title
could trip on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there's always a risk of an unintentional comment collision.
I'm going to change it to /[@#]__PURE__/
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or may be just /^\s*[@#]pure/
, so forcing it to be at the start of the comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the directive should be allowed to appear anywhere in the comment. The directive doesn't have to pretty - it's uglify, after all. ;-)
/[@#]__PURE__/
should lower the odds of accidental comment collision.
A function call or IIFE with an immediately preceding comment containing `@__PURE__` or `#__PURE__` is deemed to be a side-effect-free pure function call and can potentially be dropped.
Note: pure function call annotations work well with the new When this PR and PR #1450 are applied then toplevel IIFEs assigned to an otherwise unused var will be dropped:
|
A function call or IIFE with an immediately preceding comment containing `@__PURE__` or `#__PURE__` is deemed to be a side-effect-free pure function call and can potentially be dropped. closes mishoo#1448
In the event that all comments are retained, the pure call hint should be dropped. Otherwise this potential bug could happen if uglify output is run through uglify a second time:
A fix will be pushed shortly. |
@alexlamsl d35b92c passes on all versions of node except for 0.10.x. It's not a timeout. Do you see anything that's suspect? |
Apparently something about the var pure_regex = /[@#]__PURE__/g; |
Using a RegExp literal instead of a reused regex did the trick. Some regexp state was being mutated causing it to fail on node 0.10.x. |
Yeah, using the regex literals this way is definitely safer. I guess some states that needs resetting between |
Hmm. Looking at this, wouldn't it make more sense if the comment is removed if/when the annotated function is being removed? I'm thinking of cases where Having said that, this would only cause sub-optimal output AFAICT. So I guess it's not a major issue. |
I believe it's correct because it checks the previously set if (node.pure !== undefined) return node.pure; which outlives the removed |
Not related to this PR, but we have a similar issue with
|
Regarding your point about |
I had suspected that as well. |
With latest PR:
|
With the latest commit, (Stop me if I'm starting to sound like picking bones in a chicken egg 😅 ) |
But the case you describe still works, as far as I can tell:
It doesn't drop the hint, but that's fine. It's a no-op in that context. |
LGTM - let me fold all of this onto #1485 now. |
Yes, that's what I designed |
So |
To be clear, I wasn't saying the example above gives the wrong output, just a surprising/unexpected behaviour. What I'm puzzling over at the moment is given the comment is attached to |
If I'm not mistaken, the same comment can be attached to more than one node. A child and its parent can share the same comment depending on the statement. Ownership of a comment is a tricky thing and is ambiguous. OutputStream makes sure a given comment is output only once by marking the AST, despite multiple comment owners. |
You may be right in general, but in this case it's due to:
getting transformed into:
before
Now this looks like a bug to me. (TODO: investigate |
If you try the dump AST PR #769 and modify this line to remove the exclamation marks: https://github.com/mishoo/UglifyJS2/pull/769/files#diff-da84828c4bd7834c0040b0bbb51acdecR105 to emit the start and end tokens and execute this command you can see multiple nodes with the same comment
|
It would only be a bug if the const hint was harmful, which it is not. That transform happens because several nodes share the same comment. It's not easy to determine which node a given comment ought to belong to - particularly after a transform. |
But in my example above, |
It's harmless.
It's just a hint, it's still a |
Erm, So to modify your example a little:
|
Fair enough. I see a couple of possible solutions:
|
@alexlamsl Can we move the |
moving |
Some interest in the use of this feature: |
Would be good to know when somebody starts to use this actively in the wild. |
@alexlamsl I can confirm that this is feature pure gold and allows all classes downleveled by TypeScript to be removed by Uglify. This will have a huge impact for most Angular developers. Thank you for adding this feature. Now I'm onto figuring out how to roll this out to all TypeScript users. We'll either get our TypeScript friends to implement microsoft/TypeScript#13721 or if there is pushback against that, we'll implement it in our tsickle tool or some kind of webpack plugin within @angular/cli. We'll see. In the meantime I created this super hacky tool. Thanks again. |
Proof-of-concept Babel plugin to annotate down-levelled classes: babel/babel#5632 (comment) |
An article explaining how uglify |
&& (comments = node.start.comments_before) | ||
&& comments.length | ||
&& /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) { | ||
last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' '); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kzc any reason why the #__PURE__
comment has to be the last one? why this isnt checking all node.start.comments_before
?
If im gonna write i.e. a babel plugin which annotates calls for me it might interfere with other annotations causing i.e. such situation
var A = /*#__PURE__*/ /* @class */ function() { /* ... */ }();
and thus preventing it from being dropped as unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason why the
#__PURE__
comment has to be the last one? why this isnt checking allnode.start.comments_before
?
No particular reason other than efficiency. It probably should check all comments. PR is welcome.
Be aware that it's unlikely to be back-ported to uglify-js@2.x
which is widely used but whose code base is frozen. So you'd probably want to list #__PURE__
in the last comment to aid legacy uglify users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No particular reason other than efficiency. It probably should check all comments. PR is welcome.
PR is comming then. :)
Be aware that it's unlikely to be back-ported to uglify-js@2.x which is widely used but whose code base is frozen. So you'd probably want to list #PURE in the last comment to aid legacy uglify users.
While at the moment of adding the comment I can ensure its added as the last comment, I cannot be sure that no other transformation change it. I wouldnt be overly concern about old version of the uglify. Life goes on and library's consumers should upgrade sooner or later :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should mention that the following would still work with current uglify versions:
var A = /* @__PURE__ @class */ function() { /* ... */ }();
That's all the proposed babel plugin would have to do to retain backwards compatibility with uglify.
Fixes: #1261
A function call or IIFE with an immediately preceding comment containing
@__PURE__
or#__PURE__
is deemed to be a side-effect-free pure function call and can potentially be dropped.Related: microsoft/TypeScript#13721