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

New option to wrap function arguments #427

Closed
joseprio opened this issue Aug 17, 2019 · 11 comments
Closed

New option to wrap function arguments #427

joseprio opened this issue Aug 17, 2019 · 11 comments

Comments

@joseprio
Copy link
Contributor

Feature request

OptimizeJS is deprecated, and the wrap_iife option in Terser is a very valid replacement. However, OptimizeJS will also wrap functions that are passed as arguments under the assumption that they're likely to be called immediately. It would be nice to have an additional option in order to wrap those functions (i.e. wrap_func_args).

Happy to provide a PR.

@fabiosantoscode
Copy link
Collaborator

Could you please provide an example of what you want to happen exactly? I'm not sure I follow.

@joseprio
Copy link
Contributor Author

joseprio commented Aug 18, 2019

@fabiosantoscode the Terser wrap_iife parameter and the OptimizeJS plugin make the parsing of JS files faster in many browsers:
mishoo/UglifyJS#640

OptimizeJS has an additional optimization that Terser currently doesn't: it will wrap function arguments, so this:

a(function() { console.log("Hello world");});

Becomes this:

a((function() { console.log("Hello world");}));

The assumption here is that most function arguments will be executed immediately in that function call, which seems to be a pattern commonly used in Node-style errbacks, Promise chains, and UMD/Browserify/Webpack module declarations.

The change takes just a few lines of code:
joseprio@c3dc703

OptimizeJS is now unmaintained and it has some bugs, like in sourcemap generation; I think adding that option would allow Terser to replace that plugin functionality entirely.

@fabiosantoscode
Copy link
Collaborator

This is very interesting. Thanks for bringing this up.

I'm a bit skeptical that functions passed to other functions are invoked immediately though, node-style callbacks and promises are not called immediately. Although most other functions are.

I'm actually happy to remove the options that turn (function(){})() into !function(){}(), since besides negating the output it seems to make parsing slower. I never heard about it but I don't think 2 characters is an unfair price to pay for better speeds, and besides arrow functions need the parens anyway and they are the way of the future.

It might be nice to have @nolanlawson's input here, IIUC he used to be behind optimize.

@joseprio
Copy link
Contributor Author

joseprio commented Aug 19, 2019

I was certain that wrapping-function heuristic had a benefit, but I certainly didn't know how much would it be, so I decided to adapt a benchmark from OptimizeJS.

I ran Terser with the following options:

  • No options (default compress and mangle)
  • negate_iife + wrap_iife
  • negate_iife + OptimizeJS (in theory this should be exactly as wrap_iife and wrapping the function arguments)

Firefox 48 results:

Library Terser Terser+IIFE Terser+Opt
Create React App 18.16 15.9 12.34
Immutable 3.5 3.54 2.84
jQuery 9.27 9.1 7.84
Lodash 7.8 7.53 7
PouchDB 8.5 8.5 7.58
Three 20.34 20.72 18.4

Firefox 68 results (for some reason the resolution is very poor):

Library Terser Terser+IIFE Terser+Opt
Create React App 14 14 12
Immutable 3 3 3
jQuery 8 8 8
Lodash 8 7 7
PouchDB 8 8 7
Three 20 19 18

Chrome 76 results:

Library Terser Terser+IIFE Terser+Opt
Create React App 14.62 14.94 11.89
Immutable 3.52 3.48 3.16
jQuery 9.76 9.48 9.44
Lodash 7.53 7.98 7.02
PouchDB 7.02 7.33 7.16
Three 20.06 19.89 18.56

The wrap_func_args option I implemented in my repo generates the same output as the Terser+Opt case, which I confirmed with a diff.

@fabiosantoscode
Copy link
Collaborator

Interesting results. Have you disabled negate_iife as well in the terser and terser+opt results?

@joseprio
Copy link
Contributor Author

joseprio commented Aug 19, 2019

negate_iife is only enabled in the vanilla Terser results; I will re-run the benchmarks with it disabled.

@joseprio
Copy link
Contributor Author

joseprio commented Aug 19, 2019

The results in Chrome 76:

Library w/o Negate IIFE Negate IIFE
Create React App 14.57 14.83
Immutable 3.61 3.45
jQuery 8.19 7.52
Lodash 7.7 7.42
PouchDB 6.84 6.9
Three 18.94 19.08

In FF 48:

Library w/o Negate IIFE Negate IIFE
Create React App 14.33 14.18
Immutable 3.05 3.04
jQuery 8.36 8.3
Lodash 7.76 7.74
PouchDB 8.11 8.16
Three 20.61 20.88

@fabiosantoscode
Copy link
Collaborator

Looks like negate_iife is irrelevant here then.

@fabiosantoscode
Copy link
Collaborator

Please send a PR with your commits :) it would be cool to enable this option by default actually, so if it doesn't break too many tests please do do that. That would mean a lot of websites could immediately benefit from this change.

Thanks a lot for bringing this up!

@joseprio
Copy link
Contributor Author

@fabiosantoscode surprisingly, no tests break when I made wrap_func_args enabled by default.

About enabling it by default, I see two potential problems:

  • @nolanlawson benchmarks for Safari show a performance hit (-1.04%); Safari makes up 15% of the web browser market share thanks to iPhone
  • The output uses a few more bytes; it certainly would prove beneficial for most users and scenarios, but the change may upset people who only measure byte size

So I see this change as somewhat controversial. I think the safe option would be to add a section on the README that goes more into detail about wrap_iife and wrap_func_args, how they work, and present it as a way to improve performance in many scenarios, but with a YMMV disclaimer. I'll put the PR like I have it now, but will definitely amend it with your preferred option.

@joseprio
Copy link
Contributor Author

Created PR: #433

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

No branches or pull requests

2 participants