Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support slot attribute inside expressions #447

Merged
merged 4 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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': patch
---

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