0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Improve Astro.slots API (#2695)

* 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
This commit is contained in:
Nate Moore 2022-03-09 18:09:48 -06:00 committed by GitHub
parent af075d8157
commit ae8d925666
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 12 deletions

View file

@ -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");
}
```

View file

@ -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);
}

View file

@ -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 {

View file

@ -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;
@ -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,
@ -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;

View file

@ -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>}