Skip to content

Commit

Permalink
inline IIFEs that return a single expression
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Nov 19, 2023
1 parent 6c4aa2c commit 9fa4e79
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 4 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
document.onkeydown=o=>o.keyCode===65&&console.log("🧀");
```

In addition, immediately-invoked function expressions (IIFEs) that return a single expression are now inlined when minifying. This makes it possible to use IIFEs in combination with `@__PURE__` annotations to annotate arbitrary expressions as side-effect free without the IIFE wrapper impacting code size. For example:

```js
// Original code
const sideEffectFreeOffset = /* @__PURE__ */ (() => computeSomething())()
use(sideEffectFreeOffset)

// Old output (with --minify)
const e=(()=>computeSomething())();use(e);

// New output (with --minify)
const e=computeSomething();use(e);
```

* Automatically prefix the `mask-composite` CSS property for WebKit ([#3493](https://github.com/evanw/esbuild/issues/3493))

The `mask-composite` property will now be prefixed as `-webkit-mask-composite` for older WebKit-based browsers. In addition to prefixing the property name, handling older browsers also requires rewriting the values since WebKit uses non-standard names for the mask composite modes:
Expand Down
9 changes: 9 additions & 0 deletions internal/bundler_tests/bundler_dce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4302,6 +4302,8 @@ func TestDCEOfIIFE(t *testing.T) {
(() => { /* @__PURE__ */ removeMe() })();
var someVar;
(x => {})(someVar);
var removeThis = /* @__PURE__ */ (() => stuff())();
var removeThis2 = (() => 123)();
`,
"/keep-these.js": `
undef = (() => {})();
Expand All @@ -4310,6 +4312,13 @@ func TestDCEOfIIFE(t *testing.T) {
var someVar;
(([y]) => {})(someVar);
(({z}) => {})(someVar);
var keepThis = /* @__PURE__ */ (() => stuff())();
keepThis();
((_ = keepMe()) => {})();
var isPure = ((x, y) => 123)();
use(isPure);
var isNotPure = ((x = foo, y = bar) => 123)();
use(isNotPure);
`,
},
entryPaths: []string{
Expand Down
11 changes: 9 additions & 2 deletions internal/bundler_tests/snapshots/snapshots_dce.txt
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,7 @@ TestDCEOfIIFE
keepThisButRemoveTheIIFE;

---------- /out/keep-these.js ----------
undef = /* @__PURE__ */ (() => {
})();
undef = void 0;
keepMe();
((x = keepMe()) => {
})();
Expand All @@ -536,6 +535,14 @@ var someVar;
})(someVar);
(({ z }) => {
})(someVar);
var keepThis = stuff();
keepThis();
((_ = keepMe()) => {
})();
var isPure = /* @__PURE__ */ ((x, y) => 123)();
use(isPure);
var isNotPure = ((x = foo, y = bar) => 123)();
use(isNotPure);

================================================================================
TestDCEOfUsingDeclarations
Expand Down
6 changes: 6 additions & 0 deletions internal/js_ast/js_ast_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,7 @@ type StmtsCanBeRemovedIfUnusedFlags uint8

const (
KeepExportClauses StmtsCanBeRemovedIfUnusedFlags = 1 << iota
ReturnCanBeRemovedIfUnused
)

func StmtsCanBeRemovedIfUnused(stmts []Stmt, flags StmtsCanBeRemovedIfUnusedFlags, isUnbound func(ast.Ref) bool) bool {
Expand All @@ -2005,6 +2006,11 @@ func StmtsCanBeRemovedIfUnused(stmts []Stmt, flags StmtsCanBeRemovedIfUnusedFlag
return false
}

case *SReturn:
if (flags&ReturnCanBeRemovedIfUnused) == 0 || (s.ValueOrNil.Data != nil && !ExprCanBeRemovedIfUnused(s.ValueOrNil, isUnbound)) {
return false
}

case *SExpr:
if !ExprCanBeRemovedIfUnused(s.Value, isUnbound) {
if s.IsFromClassOrFnThatCanBeRemovedIfUnused {
Expand Down
7 changes: 5 additions & 2 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11089,8 +11089,11 @@ func (p *parser) iifeCanBeRemovedIfUnused(args []js_ast.Arg, body js_ast.FnBody)
}
}

// Check whether any statements have side effects or not
return js_ast.StmtsCanBeRemovedIfUnused(body.Block.Stmts, 0, p.isUnbound)
// Check whether any statements have side effects or not. Consider return
// statements as not having side effects because if the IIFE can be removed
// then we know the return value is unused, so we know that returning the
// value has no side effects.
return js_ast.StmtsCanBeRemovedIfUnused(body.Block.Stmts, js_ast.ReturnCanBeRemovedIfUnused, p.isUnbound)
}

type captureValueMode uint8
Expand Down
26 changes: 26 additions & 0 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,32 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
break
}
}

// Inline IIFEs that return expressions at print time
if len(e.Args) == 0 {
if arrow, ok := e.Target.Data.(*js_ast.EArrow); ok && len(arrow.Args) == 0 {
stmts := arrow.Body.Block.Stmts

// "(() => {})()" => "void 0"
if len(stmts) == 0 {
value := js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared}
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags)
break
}

// "(() => 123)()" => "123"
if len(stmts) == 1 {
if stmt, ok := stmts[0].Data.(*js_ast.SReturn); ok {
value := stmt.ValueOrNil
if value.Data == nil {
value.Data = js_ast.EUndefinedShared
}
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags)
break
}
}
}
}
}

wrap := level >= js_ast.LNew || (flags&forbidCall) != 0
Expand Down

0 comments on commit 9fa4e79

Please sign in to comment.