Skip to content

Commit

Permalink
Add function-based slot support to Astro.slots.render() (withastro#…
Browse files Browse the repository at this point in the history
…2954)

* feat(slots): add function-based slot support to Astro.slots.render()

* test(slots): add render tests
  • Loading branch information
natemoo-re committed Mar 31, 2022
1 parent 50af480 commit d81b6d9
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 5 deletions.
7 changes: 7 additions & 0 deletions .changeset/light-apricots-sort.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 19 additions & 4 deletions packages/astro/src/core/render/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down
29 changes: 29 additions & 0 deletions packages/astro/test/astro-slots.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
const { id } = Astro.props;
const content = await Astro.slots.render('default');
---

<div id={id} set:html={content} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
const { id, text } = Astro.props;
const content = await Astro.slots.render('default', [text]);
---

<div id={id} set:html={content} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
const { id } = Astro.props;
const content = await Astro.slots.render('default');
---

<div id={id} set:html={content} />
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit d81b6d9

Please sign in to comment.