summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/clean-bottles-drive.md13
-rw-r--r--packages/astro/components/Markdown.astro3
-rw-r--r--packages/astro/src/@types/astro.ts2
-rw-r--r--packages/astro/src/core/render/result.ts49
-rw-r--r--packages/astro/test/fixtures/astro-slots/src/components/SlottedAPI.astro8
5 files changed, 63 insertions, 12 deletions
diff --git a/.changeset/clean-bottles-drive.md b/.changeset/clean-bottles-drive.md
new file mode 100644
index 000000000..cb9c6af51
--- /dev/null
+++ b/.changeset/clean-bottles-drive.md
@@ -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");
+}
+```
diff --git a/packages/astro/components/Markdown.astro b/packages/astro/components/Markdown.astro
index fc666218d..0dc214732 100644
--- a/packages/astro/components/Markdown.astro
+++ b/packages/astro/components/Markdown.astro
@@ -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);
}
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index e945b8dd6..975afd192 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -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 {
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index f846ac408..fc53428e0 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -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;
diff --git a/packages/astro/test/fixtures/astro-slots/src/components/SlottedAPI.astro b/packages/astro/test/fixtures/astro-slots/src/components/SlottedAPI.astro
index e1b00fff8..36ab31739 100644
--- a/packages/astro/test/fixtures/astro-slots/src/components/SlottedAPI.astro
+++ b/packages/astro/test/fixtures/astro-slots/src/components/SlottedAPI.astro
@@ -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>}