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:
parent
af075d8157
commit
ae8d925666
5 changed files with 63 additions and 12 deletions
13
.changeset/clean-bottles-drive.md
Normal file
13
.changeset/clean-bottles-drive.md
Normal 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");
|
||||
}
|
||||
```
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>}
|
||||
|
|
Loading…
Reference in a new issue