Skip to content

Commit

Permalink
Support slot attribute inside expressions (#447)
Browse files Browse the repository at this point in the history
* fix: support conditional slots inside expressions

* feat: allow dynamic slots inside an expression

* chore: add changeset

* Update hungry-spoons-crash.md

Co-authored-by: Nate Moore <nate@astro.build>
  • Loading branch information
natemoo-re and natemoo-re committed Jul 20, 2022
1 parent dc7d553 commit 2f7cc4b
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-spoons-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/compiler': minor
---

Fix handling of `slot` attribute used inside of expressions
114 changes: 98 additions & 16 deletions internal/printer/print-to-js.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
continue
}
if a.Key == "slot" {
if n.Parent.Component {
if n.Parent.Component || n.Parent.Expression {
continue
}
// Note: if we encounter "slot" NOT inside a component, that's fine
Expand Down Expand Up @@ -521,8 +521,9 @@ func render1(p *printer, n *Node, opts RenderOptions) {
p.printTemplateLiteralClose()
p.print(`,}`)
case isComponent:
p.print(`,{`)
p.print(`,`)
slottedChildren := make(map[string][]*Node)
conditionalSlottedChildren := make([][]*Node, 0)
for c := n.FirstChild; c != nil; c = c.NextSibling {
slotProp := `"default"`
for _, a := range c.Attr {
Expand All @@ -536,6 +537,60 @@ func render1(p *printer, n *Node, opts RenderOptions) {
}
}
}
if c.Expression {
nestedSlots := make([]string, 0)
for c1 := c.FirstChild; c1 != nil; c1 = c1.NextSibling {
for _, a := range c1.Attr {
if a.Key == "slot" {
if a.Type == QuotedAttribute {
nestedSlotProp := fmt.Sprintf(`"%s"`, a.Val)
nestedSlots = append(nestedSlots, nestedSlotProp)
} else if a.Type == ExpressionAttribute {
nestedSlotProp := fmt.Sprintf(`[%s]`, a.Val)
nestedSlots = append(nestedSlots, nestedSlotProp)
} else {
panic(`unknown slot attribute type`)
}
}
}
}

if len(nestedSlots) == 1 {
slotProp = nestedSlots[0]
slottedChildren[slotProp] = append(slottedChildren[slotProp], c)
continue
} else if len(nestedSlots) > 1 {
conditionalChildren := make([]*Node, 0)
child_loop:
for c1 := c.FirstChild; c1 != nil; c1 = c1.NextSibling {
for _, a := range c1.Attr {
if a.Key == "slot" {
if a.Type == QuotedAttribute {
nestedSlotProp := fmt.Sprintf(`"%s"`, a.Val)
nestedSlots = append(nestedSlots, nestedSlotProp)
conditionalChildren = append(conditionalChildren, &Node{Type: TextNode, Data: fmt.Sprintf("{%s: () => ", nestedSlotProp), Loc: make([]loc.Loc, 1)})
conditionalChildren = append(conditionalChildren, c1)
conditionalChildren = append(conditionalChildren, &Node{Type: TextNode, Data: "}", Loc: make([]loc.Loc, 1)})
continue child_loop
} else if a.Type == ExpressionAttribute {
nestedSlotProp := fmt.Sprintf(`[%s]`, a.Val)
nestedSlots = append(nestedSlots, nestedSlotProp)
conditionalChildren = append(conditionalChildren, &Node{Type: TextNode, Data: fmt.Sprintf("{%s: () => ", nestedSlotProp), Loc: make([]loc.Loc, 1)})
conditionalChildren = append(conditionalChildren, c1)
conditionalChildren = append(conditionalChildren, &Node{Type: TextNode, Data: "}", Loc: make([]loc.Loc, 1)})
continue child_loop
} else {
panic(`unknown slot attribute type`)
}
}
}
conditionalChildren = append(conditionalChildren, c1)
}
conditionalSlottedChildren = append(conditionalSlottedChildren, conditionalChildren)
continue
}
}

// Only slot ElementNodes or non-empty TextNodes!
// CommentNode and others should not be slotted
if c.Type == ElementNode || (c.Type == TextNode && strings.TrimSpace(c.Data) != "") {
Expand All @@ -548,23 +603,50 @@ func render1(p *printer, n *Node, opts RenderOptions) {
slottedKeys = append(slottedKeys, k)
}
sort.Strings(slottedKeys)
for _, slotProp := range slottedKeys {
children := slottedChildren[slotProp]
p.print(fmt.Sprintf(`%s: () => `, slotProp))
p.printTemplateLiteralOpen()
for _, child := range children {
render1(p, child, RenderOptions{
isRoot: false,
isExpression: opts.isExpression,
depth: depth + 1,
opts: opts.opts,
printedMaybeHead: opts.printedMaybeHead,
})
if len(conditionalSlottedChildren) > 0 {
p.print(`$$mergeSlots(`)
}
p.print(`{`)
if len(slottedKeys) > 0 {
for _, slotProp := range slottedKeys {
children := slottedChildren[slotProp]
p.print(fmt.Sprintf(`%s: () => `, slotProp))
p.printTemplateLiteralOpen()
for _, child := range children {
render1(p, child, RenderOptions{
isRoot: false,
isExpression: opts.isExpression,
depth: depth + 1,
opts: opts.opts,
printedMaybeHead: opts.printedMaybeHead,
})
}
p.printTemplateLiteralClose()
p.print(`,`)
}
p.printTemplateLiteralClose()
p.print(`,`)
}
p.print(`}`)
if len(conditionalSlottedChildren) > 0 {
for _, children := range conditionalSlottedChildren {
p.print(",")
for _, child := range children {
if child.Type == ElementNode {
p.printTemplateLiteralOpen()
}
render1(p, child, RenderOptions{
isRoot: false,
isExpression: opts.isExpression,
depth: depth + 1,
opts: opts.opts,
printedMaybeHead: opts.printedMaybeHead,
})
if child.Type == ElementNode {
p.printTemplateLiteralClose()
}
}
}
p.print(`)`)
}
case isSlot:
p.print(`,`)
p.printTemplateLiteralOpen()
Expand Down
2 changes: 2 additions & 0 deletions internal/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var RENDER_HEAD = "$$renderHead"
var MAYBE_RENDER_HEAD = "$$maybeRenderHead"
var UNESCAPE_HTML = "$$unescapeHTML"
var RENDER_SLOT = "$$renderSlot"
var MERGE_SLOTS = "$$mergeSlots"
var ADD_ATTRIBUTE = "$$addAttribute"
var SPREAD_ATTRIBUTES = "$$spreadAttributes"
var DEFINE_STYLE_VARS = "$$defineStyleVars"
Expand Down Expand Up @@ -74,6 +75,7 @@ func (p *printer) printInternalImports(importSpecifier string) {
p.print("maybeRenderHead as " + MAYBE_RENDER_HEAD + ",\n ")
p.print("unescapeHTML as " + UNESCAPE_HTML + ",\n ")
p.print("renderSlot as " + RENDER_SLOT + ",\n ")
p.print("mergeSlots as " + MERGE_SLOTS + ",\n ")
p.print("addAttribute as " + ADD_ATTRIBUTE + ",\n ")
p.print("spreadAttributes as " + SPREAD_ATTRIBUTES + ",\n ")
p.print("defineStyleVars as " + DEFINE_STYLE_VARS + ",\n ")
Expand Down
29 changes: 29 additions & 0 deletions internal/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var INTERNAL_IMPORTS = fmt.Sprintf("import {\n %s\n} from \"%s\";\n", strings.J
"maybeRenderHead as " + MAYBE_RENDER_HEAD,
"unescapeHTML as " + UNESCAPE_HTML,
"renderSlot as " + RENDER_SLOT,
"mergeSlots as " + MERGE_SLOTS,
"addAttribute as " + ADD_ATTRIBUTE,
"spreadAttributes as " + SPREAD_ATTRIBUTES,
"defineStyleVars as " + DEFINE_STYLE_VARS,
Expand Down Expand Up @@ -129,6 +130,34 @@ func TestPrinter(t *testing.T) {
code: `${$$renderSlot($$result,$$slots["default"])}`,
},
},
{
name: "conditional slot",
source: `<Component>{value && <div slot="test">foo</div>}</Component>`,
want: want{
code: "${$$renderComponent($$result,'Component',Component,{},{\"test\": () => $$render`${value && $$render`${$$maybeRenderHead($$result)}<div>foo</div>`}`,})}",
},
},
{
name: "ternary slot",
source: `<Component>{Math.random() > 0.5 ? <div slot="a">A</div> : <div slot="b">B</div>}</Component>`,
want: want{
code: "${$$renderComponent($$result,'Component',Component,{},$$mergeSlots({},Math.random() > 0.5 ? {\"a\": () => $$render`${$$maybeRenderHead($$result)}<div>A</div>`} : {\"b\": () => $$render`<div>B</div>`}))}",
},
},
{
name: "function expression slots",
source: "<Component>\n{() => { switch (value) {\ncase 'a': return <div slot=\"a\">A</div>\ncase 'b': return <div slot=\"b\">B</div>\ncase 'c': return <div slot=\"c\">C</div>\n}\n}}\n</Component>",
want: want{
code: "${$$renderComponent($$result,'Component',Component,{},$$mergeSlots({},() => { switch (value) {\ncase 'a': return {\"a\": () => $$render`${$$maybeRenderHead($$result)}<div>A</div>`}\ncase 'b': return {\"b\": () => $$render`<div>B</div>`}\ncase 'c': return {\"c\": () => $$render`<div>C</div>`}}\n}))}",
},
},
{
name: "expression slot",
source: `<Component>{true && <div slot="a">A</div>}{false && <div slot="b">B</div>}</Component>`,
want: want{
code: "${$$renderComponent($$result,'Component',Component,{},{\"a\": () => $$render`${true && $$render`${$$maybeRenderHead($$result)}<div>A</div>`}`,\"b\": () => $$render`${false && $$render`<div>B</div>`}`,})}",
},
},
{
name: "preserve is:inline slot",
source: `<slot is:inline />`,
Expand Down

0 comments on commit 2f7cc4b

Please sign in to comment.