From d81b6d9ebc2176690aac3f538e84b5566acbdff4 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 31 Mar 2022 13:11:26 -0500 Subject: [PATCH] Add function-based slot support to `Astro.slots.render()` (#2954) * feat(slots): add function-based slot support to Astro.slots.render() * test(slots): add render tests --- .changeset/light-apricots-sort.md | 7 +++++ packages/astro/src/@types/astro.ts | 2 +- packages/astro/src/core/render/result.ts | 23 ++++++++++++--- packages/astro/test/astro-slots.test.js | 29 +++++++++++++++++++ .../astro-slots/src/components/Render.astro | 6 ++++ .../src/components/RenderArgs.astro | 6 ++++ .../astro-slots/src/components/RenderFn.astro | 6 ++++ .../src/pages/slottedapi-render.astro | 20 +++++++++++++ 8 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 .changeset/light-apricots-sort.md create mode 100644 packages/astro/test/fixtures/astro-slots/src/components/Render.astro create mode 100644 packages/astro/test/fixtures/astro-slots/src/components/RenderArgs.astro create mode 100644 packages/astro/test/fixtures/astro-slots/src/components/RenderFn.astro create mode 100644 packages/astro/test/fixtures/astro-slots/src/pages/slottedapi-render.astro diff --git a/.changeset/light-apricots-sort.md b/.changeset/light-apricots-sort.md new file mode 100644 index 000000000000..26a182a4087e --- /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 800ab9b23b27..0f1e48e13b94 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 & { has(slotName: string): boolean; render(slotName: string): Promise }; + slots: Record & { has(slotName: string): boolean; render(slotName: string, args?: any[]): Promise }; } export interface AstroGlobalPartial { diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index c3552be3de21..0b0f2433693b 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(); #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 f3b27aac3230..dd48837085fe 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 000000000000..c106c4d0632a --- /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'); +--- + +
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 000000000000..6936aaa10333 --- /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]); +--- + +
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 000000000000..c106c4d0632a --- /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'); +--- + +
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 000000000000..960ffa629ee2 --- /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'; +--- + + + + + + + render + {() => "render-fn"} + {(text: string) => text} + +