summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/light-apricots-sort.md7
-rw-r--r--packages/astro/src/@types/astro.ts2
-rw-r--r--packages/astro/src/core/render/result.ts23
-rw-r--r--packages/astro/test/astro-slots.test.js29
-rw-r--r--packages/astro/test/fixtures/astro-slots/src/components/Render.astro6
-rw-r--r--packages/astro/test/fixtures/astro-slots/src/components/RenderArgs.astro6
-rw-r--r--packages/astro/test/fixtures/astro-slots/src/components/RenderFn.astro6
-rw-r--r--packages/astro/test/fixtures/astro-slots/src/pages/slottedapi-render.astro20
8 files changed, 94 insertions, 5 deletions
diff --git a/.changeset/light-apricots-sort.md b/.changeset/light-apricots-sort.md
new file mode 100644
index 000000000..26a182a40
--- /dev/null
+++ b/.changeset/light-apricots-sort.md
@@ -0,0 +1,7 @@
+---
+'astro': patch
+---
+
+Improve `Astro.slots` API to support passing arguments to function-based slots.
+
+This allows for more ergonomic utility components that accept a callback function as a child.
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 800ab9b23..0f1e48e13 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -60,7 +60,7 @@ export interface AstroGlobal extends AstroGlobalPartial {
/** get information about this page */
request: Request;
/** see if slots are used */
- slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string): Promise<string> };
+ slots: Record<string, true | undefined> & { has(slotName: string): boolean; render(slotName: string, args?: any[]): Promise<string> };
}
export interface AstroGlobalPartial {
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index c3552be3d..0b0f24336 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -28,6 +28,12 @@ export interface CreateResultArgs {
request: Request;
}
+function getFunctionExpression(slot: any) {
+ if (!slot) return;
+ if (slot.expressions?.length !== 1) return;
+ return slot.expressions[0] as (...args: any[]) => any;
+}
+
class Slots {
#cache = new Map<string, string>();
#result: SSRResult;
@@ -56,15 +62,24 @@ class Slots {
return Boolean(this.#slots[name]);
}
- public async render(name: string) {
+ public async render(name: string, args: any[] = []) {
+ const cacheable = args.length === 0;
if (!this.#slots) return undefined;
- if (this.#cache.has(name)) {
+ if (cacheable && 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);
+ if (!cacheable) {
+ const component = await this.#slots[name]();
+ const expression = getFunctionExpression(component);
+ if (expression) {
+ const slot = expression(...args);
+ return await renderSlot(this.#result, slot).then((res) => res != null ? String(res) : res);
+ }
+ }
+ const content = await renderSlot(this.#result, this.#slots[name]).then((res) => res != null ? String(res) : res);
+ if (cacheable) this.#cache.set(name, content);
return content;
}
}
diff --git a/packages/astro/test/astro-slots.test.js b/packages/astro/test/astro-slots.test.js
index f3b27aac3..dd4883708 100644
--- a/packages/astro/test/astro-slots.test.js
+++ b/packages/astro/test/astro-slots.test.js
@@ -112,4 +112,33 @@ describe('Slots', () => {
expect($('#default')).to.have.lengthOf(1); // the default slot is filled
}
});
+
+ it('Slots.render() API', async () => {
+ // Simple imperative slot render
+ {
+ const html = await fixture.readFile('/slottedapi-render/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('#render')).to.have.lengthOf(1);
+ expect($('#render').text()).to.equal('render');
+ }
+
+ // Child function render without args
+ {
+ const html = await fixture.readFile('/slottedapi-render/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('#render-fn')).to.have.lengthOf(1);
+ expect($('#render-fn').text()).to.equal('render-fn');
+ }
+
+ // Child function render with args
+ {
+ const html = await fixture.readFile('/slottedapi-render/index.html');
+ const $ = cheerio.load(html);
+
+ expect($('#render-args')).to.have.lengthOf(1);
+ expect($('#render-args').text()).to.equal('render-args');
+ }
+ });
});
diff --git a/packages/astro/test/fixtures/astro-slots/src/components/Render.astro b/packages/astro/test/fixtures/astro-slots/src/components/Render.astro
new file mode 100644
index 000000000..c106c4d06
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-slots/src/components/Render.astro
@@ -0,0 +1,6 @@
+---
+const { id } = Astro.props;
+const content = await Astro.slots.render('default');
+---
+
+<div id={id} set:html={content} />
diff --git a/packages/astro/test/fixtures/astro-slots/src/components/RenderArgs.astro b/packages/astro/test/fixtures/astro-slots/src/components/RenderArgs.astro
new file mode 100644
index 000000000..6936aaa10
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-slots/src/components/RenderArgs.astro
@@ -0,0 +1,6 @@
+---
+const { id, text } = Astro.props;
+const content = await Astro.slots.render('default', [text]);
+---
+
+<div id={id} set:html={content} />
diff --git a/packages/astro/test/fixtures/astro-slots/src/components/RenderFn.astro b/packages/astro/test/fixtures/astro-slots/src/components/RenderFn.astro
new file mode 100644
index 000000000..c106c4d06
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-slots/src/components/RenderFn.astro
@@ -0,0 +1,6 @@
+---
+const { id } = Astro.props;
+const content = await Astro.slots.render('default');
+---
+
+<div id={id} set:html={content} />
diff --git a/packages/astro/test/fixtures/astro-slots/src/pages/slottedapi-render.astro b/packages/astro/test/fixtures/astro-slots/src/pages/slottedapi-render.astro
new file mode 100644
index 000000000..960ffa629
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-slots/src/pages/slottedapi-render.astro
@@ -0,0 +1,20 @@
+---
+import Render from '../components/Render.astro';
+import RenderFn from '../components/RenderFn.astro';
+import RenderArgs from '../components/RenderArgs.astro';
+---
+
+<html>
+ <head>
+ <!--
+ Test Astro.slots.render behavior.
+ - `Render` is basic imperative `render` call
+ - `RenderFn` is `render` that calls child function with arguments
+ -->
+ </head>
+ <body>
+ <Render id="render">render</Render>
+ <RenderFn id="render-fn">{() => "render-fn"}</RenderFn>
+ <RenderArgs id="render-args" text="render-args">{(text: string) => text}</RenderArgs>
+ </body>
+</html>