Skip to content

Commit

Permalink
tree shaking: handle destructuring of an array
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Nov 19, 2023
1 parent f361c7f commit 00fa010
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 23 deletions.
28 changes: 28 additions & 0 deletions internal/bundler_tests/bundler_dce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4335,6 +4335,34 @@ func TestDCEOfIIFE(t *testing.T) {
})
}

func TestDCEOfDestructuring(t *testing.T) {
dce_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
// Identifier bindings
var remove1
var remove2 = null
var KEEP1 = x
// Array patterns
var [remove3] = []
var [remove4, ...remove5] = [...[1, 2], 3]
var [, , remove6] = [, , 3]
var [KEEP2] = [x]
var [KEEP3] = [...{}]
// Object patterns (not handled right now)
var { KEEP4 } = {}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
},
})
}

func TestDCEOfDecorators(t *testing.T) {
dce_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
3 changes: 1 addition & 2 deletions internal/bundler_tests/bundler_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8187,9 +8187,8 @@ func TestCommentPreservation(t *testing.T) {
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
Mode: config.ModePassThrough,
AbsOutputDir: "/out",
OutputFormat: config.FormatCommonJS,
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{
Exact: map[string]bool{"foo": true},
Expand Down
9 changes: 9 additions & 0 deletions internal/bundler_tests/snapshots/snapshots_dce.txt
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ var StaticAccessor = class {
static accessor accessor;
};

================================================================================
TestDCEOfDestructuring
---------- /out/entry.js ----------
// entry.js
var KEEP1 = x;
var [KEEP2] = [x];
var [KEEP3] = [...{}];
var { KEEP4 } = {};

================================================================================
TestDCEOfExperimentalDecorators
---------- /out/keep-these.js ----------
Expand Down
35 changes: 17 additions & 18 deletions internal/bundler_tests/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ export {
================================================================================
TestCommentPreservation
---------- /out/entry.js ----------
// entry.js
console.log(
import(
/* before */
Expand Down Expand Up @@ -393,54 +392,54 @@ console.log(
/* after */
)
);
var [
let [
/* foo */
] = [
/* bar */
];
var [
let [
// foo
] = [
// bar
];
var [
let [
/*before*/
...s
] = [
/*before*/
...s
];
var [.../*before*/
let [.../*before*/
s2] = [.../*before*/
s2];
var {
let {
/* foo */
} = {
/* bar */
};
var {
let {
// foo
} = {
// bar
};
var {
let {
/*before*/
...s3
} = {
/*before*/
...s3
};
var { .../*before*/
let { .../*before*/
s4 } = { .../*before*/
s4 };
var [
let [
/* before */
x
] = [
/* before */
x
];
var [
let [
/* before */
x2
/* after */
Expand All @@ -449,7 +448,7 @@ var [
x2
/* after */
];
var [
let [
// before
x3
// after
Expand All @@ -458,14 +457,14 @@ var [
x3
// after
];
var {
let {
/* before */
y
} = {
/* before */
y
};
var {
let {
/* before */
y2
/* after */
Expand All @@ -474,7 +473,7 @@ var {
y2
/* after */
};
var {
let {
// before
y3
// after
Expand All @@ -483,21 +482,21 @@ var {
y3
// after
};
var {
let {
/* before */
[y4]: y4
} = {
/* before */
[y4]: y4
};
var { [
let { [
/* before */
y5
]: y5 } = { [
/* before */
y5
]: y5 };
var { [
let { [
y6
/* after */
]: y6 } = { [
Expand Down
45 changes: 42 additions & 3 deletions internal/js_ast/js_ast_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2035,14 +2035,46 @@ func StmtsCanBeRemovedIfUnused(stmts []Stmt, flags StmtsCanBeRemovedIfUnusedFlag
}

for _, decl := range s.Decls {
if _, ok := decl.Binding.Data.(*BIdentifier); !ok {
// Check that the bindings are side-effect free
switch binding := decl.Binding.Data.(type) {
case *BIdentifier:
// An identifier binding has no side effects

case *BArray:
// Destructuring the initializer has no side effects if the
// initializer is an array, since we assume the iterator is then
// the built-in side-effect free array iterator.
if _, ok := decl.ValueOrNil.Data.(*EArray); ok {
for _, item := range binding.Items {
if item.DefaultValueOrNil.Data != nil && !ExprCanBeRemovedIfUnused(item.DefaultValueOrNil, isUnbound) {
return false
}

switch item.Binding.Data.(type) {
case *BIdentifier, *BMissing:
// Right now we only handle an array pattern with identifier
// bindings or with empty holes (i.e. "missing" elements)
default:
return false
}
}
break
}
return false

default:
// Consider anything else to potentially have side effects
return false
}

// Check that the initializer is side-effect free
if decl.ValueOrNil.Data != nil {
if !ExprCanBeRemovedIfUnused(decl.ValueOrNil, isUnbound) {
return false
} else if s.Kind.IsUsing() {
// "using" declarations are only side-effect free if they are initialized to null or undefined
}

// "using" declarations are only side-effect free if they are initialized to null or undefined
if s.Kind.IsUsing() {
if t := KnownPrimitiveType(decl.ValueOrNil.Data); t != PrimitiveNull && t != PrimitiveUndefined {
return false
}
Expand Down Expand Up @@ -2261,6 +2293,13 @@ func ExprCanBeRemovedIfUnused(expr Expr, isUnbound func(ast.Ref) bool) bool {

case *EArray:
for _, item := range e.Items {
if spread, ok := item.Data.(*ESpread); ok {
if _, ok := spread.Value.Data.(*EArray); ok {
// Spread of an inline array such as "[...[x]]" is side-effect free
item = spread.Value
}
}

if !ExprCanBeRemovedIfUnused(item, isUnbound) {
return false
}
Expand Down

0 comments on commit 00fa010

Please sign in to comment.