Skip to content

Commit

Permalink
minify static object spread in jsx props
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Sep 18, 2022
1 parent c9b24d8 commit a6acbaa
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 41 deletions.
94 changes: 53 additions & 41 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11870,6 +11870,49 @@ func (p *parser) mangleTemplate(loc logger.Loc, e *js_ast.ETemplate) js_ast.Expr
return js_ast.Expr{Loc: loc, Data: e}
}

func (p *parser) mangleObjectSpread(properties []js_ast.Property) []js_ast.Property {
var result []js_ast.Property
for _, property := range properties {
if property.Kind == js_ast.PropertySpread {
switch v := property.ValueOrNil.Data.(type) {
case *js_ast.EBoolean, *js_ast.ENull, *js_ast.EUndefined, *js_ast.ENumber,
*js_ast.EBigInt, *js_ast.ERegExp, *js_ast.EFunction, *js_ast.EArrow:
// This value is ignored because it doesn't have any of its own properties
continue

case *js_ast.EObject:
for i, p := range v.Properties {
// Getters are evaluated at iteration time. The property
// descriptor is not inlined into the caller. Since we are not
// evaluating code at compile time, just bail if we hit one
// and preserve the spread with the remaining properties.
if p.Kind == js_ast.PropertyGet || p.Kind == js_ast.PropertySet {
v.Properties = v.Properties[i:]
result = append(result, property)
break
}

// Also bail if we hit a verbatim "__proto__" key. This will
// actually set the prototype of the object being spread so
// inlining it is not correct.
if p.Kind == js_ast.PropertyNormal && !p.Flags.Has(js_ast.PropertyIsComputed) && !p.Flags.Has(js_ast.PropertyIsMethod) {
if str, ok := p.Key.Data.(*js_ast.EString); ok && helpers.UTF16EqualsString(str.Value, "__proto__") {
v.Properties = v.Properties[i:]
result = append(result, property)
break
}
}

result = append(result, p)
}
continue
}
}
result = append(result, property)
}
return result
}

func containsClosingScriptTag(text string) bool {
for {
i := strings.Index(text, "</")
Expand Down Expand Up @@ -12256,8 +12299,11 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
}

// Visit properties
hasSpread := false
for i, property := range e.Properties {
if property.Kind != js_ast.PropertySpread {
if property.Kind == js_ast.PropertySpread {
hasSpread = true
} else {
if mangled, ok := property.Key.Data.(*js_ast.EMangledProp); ok {
mangled.Ref = p.symbolForMangledProp(p.loadNameFromRef(mangled.Ref))
} else {
Expand All @@ -12273,6 +12319,11 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
e.Properties[i] = property
}

// "{a, ...{b, c}, d}" => "{a, b, c, d}"
if p.options.minifySyntax && hasSpread {
e.Properties = p.mangleObjectSpread(e.Properties)
}

// Visit children
if len(e.Children) > 0 {
for i, child := range e.Children {
Expand Down Expand Up @@ -13802,46 +13853,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
if in.assignTarget == js_ast.AssignTargetNone {
// "{a, ...{b, c}, d}" => "{a, b, c, d}"
if p.options.minifySyntax && hasSpread {
var properties []js_ast.Property
for _, property := range e.Properties {
if property.Kind == js_ast.PropertySpread {
switch v := property.ValueOrNil.Data.(type) {
case *js_ast.EBoolean, *js_ast.ENull, *js_ast.EUndefined, *js_ast.ENumber,
*js_ast.EBigInt, *js_ast.ERegExp, *js_ast.EFunction, *js_ast.EArrow:
// This value is ignored because it doesn't have any of its own properties
continue

case *js_ast.EObject:
for i, p := range v.Properties {
// Getters are evaluated at iteration time. The property
// descriptor is not inlined into the caller. Since we are not
// evaluating code at compile time, just bail if we hit one
// and preserve the spread with the remaining properties.
if p.Kind == js_ast.PropertyGet || p.Kind == js_ast.PropertySet {
v.Properties = v.Properties[i:]
properties = append(properties, property)
break
}

// Also bail if we hit a verbatim "__proto__" key. This will
// actually set the prototype of the object being spread so
// inlining it is not correct.
if p.Kind == js_ast.PropertyNormal && !p.Flags.Has(js_ast.PropertyIsComputed) && !p.Flags.Has(js_ast.PropertyIsMethod) {
if str, ok := p.Key.Data.(*js_ast.EString); ok && helpers.UTF16EqualsString(str.Value, "__proto__") {
v.Properties = v.Properties[i:]
properties = append(properties, property)
break
}
}

properties = append(properties, p)
}
continue
}
}
properties = append(properties, property)
}
e.Properties = properties
e.Properties = p.mangleObjectSpread(e.Properties)
}

// Object expressions represent both object literals and binding patterns.
Expand Down
22 changes: 22 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ func expectPrintedJSX(t *testing.T, contents string, expected string) {
})
}

func expectPrintedMangleJSX(t *testing.T, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents, expected, config.Options{
MinifySyntax: true,
JSX: config.JSXOptions{
Parse: true,
},
})
}

type JSXAutomaticTestOptions struct {
Development bool
ImportSource string
Expand Down Expand Up @@ -3693,6 +3703,18 @@ func TestMangleObject(t *testing.T) {
expectPrintedMangle(t, "x = {y() {}}?.y()", "x = { y() {\n} }.y();\n")
}

func TestMangleObjectJSX(t *testing.T) {
expectPrintedJSX(t, "x = <foo bar {...{}} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n ...{}\n});\n")
expectPrintedJSX(t, "x = <foo bar {...null} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n ...null\n});\n")
expectPrintedJSX(t, "x = <foo bar {...{bar}} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n ...{ bar }\n});\n")
expectPrintedJSX(t, "x = <foo bar {...bar} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n ...bar\n});\n")

expectPrintedMangleJSX(t, "x = <foo bar {...{}} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true\n});\n")
expectPrintedMangleJSX(t, "x = <foo bar {...null} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true\n});\n")
expectPrintedMangleJSX(t, "x = <foo bar {...{bar}} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n bar\n});\n")
expectPrintedMangleJSX(t, "x = <foo bar {...bar} />", "x = /* @__PURE__ */ React.createElement(\"foo\", {\n bar: true,\n ...bar\n});\n")
}

func TestMangleArrow(t *testing.T) {
expectPrintedMangle(t, "var a = () => {}", "var a = () => {\n};\n")
expectPrintedMangle(t, "var a = () => 123", "var a = () => 123;\n")
Expand Down

0 comments on commit a6acbaa

Please sign in to comment.