Hoist predictable functions calls that are only used once
- Dominant language
- JavaScript
- Stars
- 4.4k
- Forks
- 217
- PR merge metrics
- No merged PRs in 30d
Description
Under 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
```js
(() => {
function noop() {
// Begin function body
// End function body
}
// More stuff going on here…
noop();
})()
```
#### out
```js
(() => {
// More stuff going on here…
// Begin function body
// End function body
})()
```
### Simple without arguments (Works for arrow functions)
#### in
```js
(() => {
let a;
function fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
```
#### out
```js
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
```
### Using a function assignment (Works for arrow functions)
#### in
```js
(() => {
let a;
const fn = fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
```
#### out
```js
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
```
### Using a function assignment (Works for arrow functions)
#### in
```js
(() => {
let a;
const fn = () => {
// Begin function body
a++;
// End function body
}
// More stuff going on here…
fn();
})()
```
#### out
```js
(() => {
let a;
// More stuff going on here…
// Begin function body
a++;
// End function body
})()
```
### Handling function arguments (Works for arrow functions)
#### in
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
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
```js
(() => {
let a;
function fn() {
// Begin function body
return 1337;
// End function body
}
// More stuff going on here…
a = fn();
})()
```
#### out
```js
(() => {
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
```js
(() => {
let a;
(function(b) {
// Begin function body
a += b
// End function body
})(6)
})()
```
#### out
```js
(() => {
let a;
// Function arguments
let __fn_argument_0 = 6;
// Begin function body
a += __fn_argument_0;
// End function body
})()
```
### Binding this using `.call()`
#### in
```js
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.call({foo: 'bar'}, 'b');
})()
```
#### out
```js
(() => {
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
```js
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.apply({foo: 'bar'}, ['b']);
})()
```
#### out
```js
(() => {
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`
```js
(() => {
function fn() {
// Begin function body
// End function body
}
// More stuff going on here…
new fn();
})()
```
### Conditional return
```js
(() => {
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.
```js
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on here…
fn.apply({foo: 'bar'}, params);
})()
```
```js
(() => {
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
```js
(() => {
function fn(b) {
// Begin function body
Array.prototype.map.apply(arguments);
// End function body
}
// More stuff going on here…
fn();
})()
```
```js
(() => {
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
```js
(() => {
function fn(b) {
// Begin function body
// End function body
}
fn.prop = foo();
// More stuff going on here…
fn();
})()
```
#### Out
```js
(() => {
foo();
// More stuff going on here…
// Begin function body
// End function body
})()
```
Contributor guide
Assessment
This issue has not been assessed yet.