babel / babel/minify

Hoist predictable functions calls that are only used once

Open
#700 6 comments 3 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.