Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/orange-lands-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/compiler": patch
---

Fix slot attribute stripped inside expression
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

[TestPrinter/Preserve_slot_attribute_at_root_level_in_expression - 1]
## Input

```
{!href ? <button slot={slotName}>Button</button> : <a href={href} slot={slotName}>Link</a>}
```

## Output

```js
import {
Fragment,
render as $$render,
createAstro as $$createAstro,
createComponent as $$createComponent,
renderComponent as $$renderComponent,
renderHead as $$renderHead,
maybeRenderHead as $$maybeRenderHead,
unescapeHTML as $$unescapeHTML,
renderSlot as $$renderSlot,
mergeSlots as $$mergeSlots,
addAttribute as $$addAttribute,
spreadAttributes as $$spreadAttributes,
defineStyleVars as $$defineStyleVars,
defineScriptVars as $$defineScriptVars,
renderTransition as $$renderTransition,
createTransitionScope as $$createTransitionScope,
renderScript as $$renderScript,
createMetadata as $$createMetadata
} from "http://localhost:3000/";

export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [] });

const $$Component = $$createComponent(($$result, $$props, $$slots) => {

return $$render`${!href ? $$render`${$$maybeRenderHead($$result)}<button${$$addAttribute(slotName, "slot")}>Button</button>` : $$render`<a${$$addAttribute(href, "href")}${$$addAttribute(slotName, "slot")}>Link</a>`}`;
}, undefined, undefined);
export default $$Component;
```
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

[TestPrinter/Preserve_slot_attribute_in_conditional_expression_for_custom_element - 1]
## Input

```
<body><my-element>{show && <div slot="content">Content</div>}</my-element></body>
```

## Output

```js
import {
Fragment,
render as $$render,
createAstro as $$createAstro,
createComponent as $$createComponent,
renderComponent as $$renderComponent,
renderHead as $$renderHead,
maybeRenderHead as $$maybeRenderHead,
unescapeHTML as $$unescapeHTML,
renderSlot as $$renderSlot,
mergeSlots as $$mergeSlots,
addAttribute as $$addAttribute,
spreadAttributes as $$spreadAttributes,
defineStyleVars as $$defineStyleVars,
defineScriptVars as $$defineScriptVars,
renderTransition as $$renderTransition,
createTransitionScope as $$createTransitionScope,
renderScript as $$renderScript,
createMetadata as $$createMetadata
} from "http://localhost:3000/";

export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [] });

const $$Component = $$createComponent(($$result, $$props, $$slots) => {

return $$render`${$$maybeRenderHead($$result)}<body>${$$renderComponent($$result,'my-element','my-element',{},{"default": () => $$render`${show && $$render`<div slot="content">Content</div>`}`,})}</body>`;
}, undefined, undefined);
export default $$Component;
```
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

[TestPrinter/Preserve_slot_attribute_in_expression_for_custom_element - 1]
## Input

```
<body><my-element>{!href ? <button slot={slotName}>Button</button> : <a href={href} slot={slotName}>Link</a>}</my-element></body>
```

## Output

```js
import {
Fragment,
render as $$render,
createAstro as $$createAstro,
createComponent as $$createComponent,
renderComponent as $$renderComponent,
renderHead as $$renderHead,
maybeRenderHead as $$maybeRenderHead,
unescapeHTML as $$unescapeHTML,
renderSlot as $$renderSlot,
mergeSlots as $$mergeSlots,
addAttribute as $$addAttribute,
spreadAttributes as $$spreadAttributes,
defineStyleVars as $$defineStyleVars,
defineScriptVars as $$defineScriptVars,
renderTransition as $$renderTransition,
createTransitionScope as $$createTransitionScope,
renderScript as $$renderScript,
createMetadata as $$createMetadata
} from "http://localhost:3000/";

export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [] });

const $$Component = $$createComponent(($$result, $$props, $$slots) => {

return $$render`${$$maybeRenderHead($$result)}<body>${$$renderComponent($$result,'my-element','my-element',{},{"default": () => $$render`${!href ? $$render`<button${$$addAttribute(slotName, "slot")}>Button</button>` : $$render`<a${$$addAttribute(href, "href")}${$$addAttribute(slotName, "slot")}>Link</a>`}`,})}</body>`;
}, undefined, undefined);
export default $$Component;
```
---
11 changes: 9 additions & 2 deletions internal/printer/print-to-js.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,11 +482,18 @@ func render1(p *printer, n *Node, opts RenderOptions) {
}

if a.Key == "slot" {
if n.Parent.Component || n.Parent.Expression {
// Walk up the tree to find the nearest non-Expression ancestor
// to determine if we're inside an Astro Component (slot should be stripped)
// or a CustomElement/regular HTML (slot should be preserved)
parent := n.Parent
for parent != nil && parent.Expression {
parent = parent.Parent
}
if parent != nil && parent.Component {
continue
}
// Note: if we encounter "slot" NOT inside a component, that's fine
// These should be preserved in the output
// These should be preserved in the output (e.g., for web components)
p.printAttribute(a, n)
} else if a.Key == "data-astro-source-file" {
p.printAttribute(a, n)
Expand Down
20 changes: 16 additions & 4 deletions internal/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,25 +1208,37 @@ import { Container, Col, Row } from 'react-bootstrap';
source: `<body><Component><Fragment slot=named><div>Default</div><div>Named</div></Fragment></Component></body>`,
},
{
name: "Fragment with await",
name: "Fragment with await",
source: `<body><Fragment> { await Promise.resolve("Awaited") } </Fragment></body>`,
},
{
name: "Fragment shorthand with await",
name: "Fragment shorthand with await",
source: `<body><> { await Promise.resolve("Awaited") } </></body>`,
},
{
name: "Fragment wrapping link with awaited href",
name: "Fragment wrapping link with awaited href",
source: `<head><Fragment><link rel="preload" href={(await import('../fonts/some-font.woff2')).default} as="font" crossorigin /></Fragment></head>`,
},
{
name: "Component with await",
name: "Component with await",
source: `<body><Component> { await Promise.resolve("Awaited") } </Component></body>`,
},
{
name: "Preserve slots inside custom-element",
source: `<body><my-element><div slot=name>Name</div><div>Default</div></my-element></body>`,
},
{
name: "Preserve slot attribute in expression for custom element",
source: `<body><my-element>{!href ? <button slot={slotName}>Button</button> : <a href={href} slot={slotName}>Link</a>}</my-element></body>`,
},
{
name: "Preserve slot attribute in conditional expression for custom element",
source: `<body><my-element>{show && <div slot="content">Content</div>}</my-element></body>`,
},
{
name: "Preserve slot attribute at root level in expression",
source: `{!href ? <button slot={slotName}>Button</button> : <a href={href} slot={slotName}>Link</a>}`,
},
{
name: "Preserve namespaces",
source: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect xlink:href="#id"></svg>`,
Expand Down