Skip to content

Commit

Permalink
Improve Astro.slots API (#2695)
Browse files Browse the repository at this point in the history
* feat: update Astro.slots API

* fix: migrate Markdown to public `Astro.slots.render` API

* chore: update internal AstroGlobal types

* chore: add changeset

* Update clean-bottles-drive.md

* refactor(test): update slot tests to new syntax
  • Loading branch information
natemoo-re committed Mar 10, 2022
1 parent af075d8 commit ae8d925
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 12 deletions.
13 changes: 13 additions & 0 deletions .changeset/clean-bottles-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'astro': patch
---

Update `Astro.slots` API with new public `has` and `render` methods.

This is a backwards-compatible change—`Astro.slots.default` will still be `true` if the component has been passed a `default` slot.

```ts
if (Astro.slots.has("default")) {
const content = await Astro.slots.render("default");
}
```
3 changes: 1 addition & 2 deletions packages/astro/components/Markdown.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const { privateRenderMarkdownDoNotUse: renderMarkdown } = Astro as any;
// If no content prop provided, use the slot.
if (!content) {
const { privateRenderSlotDoNotUse: renderSlot } = Astro as any;
content = await renderSlot('default');
content = await Astro.slots.render('default');
if (content !== undefined && content !== null) {
content = dedent(content);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface AstroGlobal extends AstroGlobalPartial {
params: Params;
};
/** see if slots are used */
slots: Record<string, true | undefined>;
slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string): Promise<string> };
}

export interface AstroGlobalPartial {
Expand Down
49 changes: 44 additions & 5 deletions packages/astro/src/core/render/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,47 @@ export interface CreateResultArgs {
scripts?: Set<SSRElement>;
}

class Slots {
#cache = new Map<string, string>();
#result: SSRResult;
#slots: Record<string, any> | null;

constructor(result: SSRResult, slots: Record<string, any> | null) {
this.#result = result;
this.#slots = slots;
if (slots) {
for (const key of Object.keys(slots)) {
if ((this as any)[key] !== undefined) {
throw new Error(`Unable to create a slot named "${key}". "${key}" is a reserved slot name!\nPlease update the name of this slot.`)
}
Object.defineProperty(this, key, {
get() {
return true;
},
enumerable: true
})
}
}
}

public has(name: string) {
if (!this.#slots) return false;
return Boolean(this.#slots[name]);
}

public async render(name: string) {
if (!this.#slots) return undefined;
if (this.#cache.has(name)) {
const result = this.#cache.get(name)
return result;
};
if (!this.has(name)) return undefined;
const content = await renderSlot(this.#result, this.#slots[name]).then(res => res != null ? res.toString() : res);
this.#cache.set(name, content);
return content;
}
}

export function createResult(args: CreateResultArgs): SSRResult {
const { legacyBuild, origin, markdownRender, params, pathname, renderers, resolve, site: buildOptionsSite } = args;

Expand All @@ -36,6 +77,8 @@ export function createResult(args: CreateResultArgs): SSRResult {
const site = new URL(origin);
const url = new URL('.' + pathname, site);
const canonicalURL = getCanonicalURL('.' + pathname, buildOptionsSite || origin);
const astroSlots = new Slots(result, slots);

return {
__proto__: astroGlobal,
props,
Expand Down Expand Up @@ -79,11 +122,7 @@ ${extra}`

return astroGlobal.resolve(path);
},
slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
// This is used for <Markdown> but shouldn't be used publicly
privateRenderSlotDoNotUse(slotName: string) {
return renderSlot(result, slots ? slots[slotName] : null);
},
slots: astroSlots,
// <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
async privateRenderMarkdownDoNotUse(content: string, opts: any) {
let [mdRender, renderOpts] = markdownRender;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{Astro.slots.a && <div id="a">
{Astro.slots.has("a") && <div id="a">
<slot name="a" />
</div>}

{Astro.slots.b && <div id="b">
{Astro.slots.has("b") && <div id="b">
<slot name="b" />
</div>}

{Astro.slots.c && <div id="c">
{Astro.slots.has("c") && <div id="c">
<slot name="c" />
</div>}

{Astro.slots.default && <div id="default">
{Astro.slots.has("default") && <div id="default">
<slot />
</div>}

0 comments on commit ae8d925

Please sign in to comment.