Hoist predictable functions calls that are only used once
See original GitHub issueUnder some conditions it is possible to safely replace a function call with the body of the called function.
I believe the following conditions must be met:
- The function may only be used once.
- Its use must be a call expression.
- It may not be called with
new
. - It must be called in either the same scope where it is declared or in a child scope of that.
- It may not be called with variable arguments (spread or
apply
). - It may not use the
arguments
object in an unpredicable way, i.e. using loops or passing it around. - It may not return conditionally.
I believe this might have a pretty big impact on file size, because:
- It might further hoist scopes after webpack’s ModuleConcatenationPlugin has done it’s job on a module level, because it would further hoist module level initialization logic.
- It might remove IIFEs nested inside other IIFEs added by build tools.
- It might imply the complete removal of functions that have been turned into noops in the build process.
Transforms
noop
in
(() => {
function noop() {
// Begin function body
// End function body
}
// More stuff going on here…
noop();
})()
out
(() => {
// More stuff going on here…
// Begin function body
// End function body
})()
Simple without arguments (Works for arrow functions)
in
(() => {
let a;
function fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
out
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
Using a function assignment (Works for arrow functions)
in
(() => {
let a;
const fn = fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
out
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
Using a function assignment (Works for arrow functions)
in
(() => {
let a;
const fn = () => {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
out
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
Handling function arguments (Works for arrow functions)
in
(() => {
let a;
function fn(b, c) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
a *= arguments[0]
// End function body
}
// More stuff going on here…
fn(42);
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_argument_0 = 42,
__fn_argument_1;
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
a *= __fn_argument_0
// End function body
})()
Handling function spread params (Works for arrow functions)
in
(() => {
let a;
function fn(b, ...params) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
a -= params[0];
a *= params[1];
a /= params[2];
a -= arguments[0];
a *= arguments[1];
a /= arguments[2];
// End function body
}
// More stuff going on here…
fn(1, 2, 3);
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_argument_0 = 1,
__fn_rest_params = [2, 3];
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
a -= __fn_rest_params[0];
a *= __fn_rest_params[1];
a /= __fn_rest_params[2];
a -= __fn_argument_0;
a *= __fn_rest_params[0];
a /= __fn_rest_params[1];
// End function body
})()
Handling additional arguments (Works for arrow functions)
in
(() => {
let a;
function fn(b) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
// End function body
}
// More stuff going on here…
fn(1, 2, 3);
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_argument_0 = 1;
2;
3;
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
// End function body
})()
Arguments reassignment (Works for arrow functions)
in
(() => {
let a;
function fn(b) {
// Begin function body
// Simple function argument
b = 1337
// This is the same as using b
a *= arguments[0]
// End function body
}
// More stuff going on here…
fn(42);
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_argument_0 = 42;
// Simple function argument
__fn_argument_0 = 1337
// This is the same as using b
a *= __fn_argument_0
// End function body
})()
End return statement (Works for arrow functions)
in
(() => {
let a;
function fn() {
// Begin function body
return 1337;
// End function body
}
// More stuff going on here…
a = fn();
})()
out
(() => {
let a;
// More stuff going on here…
// Simple function argument
// This is the same as using b
a = 1337;
// End function body
})()
Handling direct function calls (Works for arrow functions)
in
(() => {
let a;
(function(b) {
// Begin function body
a += b
// End function body
})(6)
})()
out
(() => {
let a;
// Function arguments
let __fn_argument_0 = 6;
// Begin function body
a += __fn_argument_0;
// End function body
})()
Binding this using .call()
in
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.call({foo: 'bar'}, 'b');
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_this = {foo: 'bar'};
__fn_argument_0 = 'b';
// Begin function body
__fn_this.b = __fn_argument_0;
// End function body
})()
Binding this using .apply()
in
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.apply({foo: 'bar'}, ['b']);
})()
out
(() => {
let a;
// More stuff going on here…
// Function arguments
let __fn_this = {foo: 'bar'};
__fn_argument_0 = 'b';
// Begin function body
__fn_this.b = __fn_argument_0;
// End function body
})()
Unresolved situations
Using new
(() => {
function fn() {
// Begin function body
// End function body
}
// More stuff going on here…
new fn();
})()
Conditional return
(() => {
function fn() {
// Begin function body
if (condition) {
return 'so true'
}
// Do more stuff
// End function body
}
// More stuff going on here…
fn();
})()
Variable apply()
arguments.
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.apply({foo: 'bar'}, params);
})()
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.apply({foo: 'bar'}, ['b', ...params]);
})()
Non standard way of using aruments
(() => {
function fn(b) {
// Begin function body
Array.prototype.map.apply(arguments);
// End function body
}
// More stuff going on here…
fn();
})()
(() => {
function fn(b) {
// Begin function body
console.log(arguments[i]);
// End function body
}
// More stuff going on here…
fn();
})()
Corner cases
I believe it is safe to remove assignments to functions if they are used in this simple way.
In
(() => {
function fn(b) {
// Begin function body
// End function body
}
fn.prop = foo();
// More stuff going on here…
fn();
})()
Out
(() => {
foo();
// More stuff going on here…
// Begin function body
// End function body
})()
Issue Analytics
- State:
- Created 6 years ago
- Reactions:3
- Comments:6 (4 by maintainers)
Top Results From Across the Web
Understanding Variables, Scope, and Hoisting in JavaScript
This tutorial covers what variables are, how to declare and name them, and also take a closer look at the difference between var,...
Read more >A Bay Area Man's 1953 'Prophecy' Predicted Smartphones ...
A Bay Area Man's 1953 'Prophecy' Predicted Smartphones, Video Calls and Apple Watches.
Read more >Optimize Options (Using the GNU Compiler ...
Consider all static functions called once for inlining into their caller even if they are not marked inline . If a call to...
Read more >caret: Classification and Regression Training
Description Misc functions for training and plotting classification and regression models.
Read more >typescript spread arguments type
TypeScript Returns a string representing the calling array and its elements. ... If a type parameter is only used once in the function...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@qm3ster @j-f1 This would really depend on the function body and how much it could be further optimized.
An obvious case in which the function could be inlined multiple times, is if the function is a no-op.
For other cases it would be hard to determine whether this would make the code longer or shorter.
It could be interesting to eventually transform this:
Into this:
And then this:
However, until someone finds a solution to determine this, I think it’s better not to inline the function body twice.
The shadowing variable should be renamed. I’m not very familiar with Babel internals, but I do believe I’ve seen function for creating unique variable names somewhere.
This situation is very similar: