diff options
author | 2023-04-07 11:04:31 -0400 | |
---|---|---|
committer | 2023-04-07 11:04:31 -0400 | |
commit | fa84f1a7d2c290479c75199f16e8de489036d7ea (patch) | |
tree | 7f399d4d66bea2509488c86189ab4c551542ab3f | |
parent | f2112452ad5b0855555957d7a044900d24dc7ea0 (diff) | |
download | astro-fa84f1a7d2c290479c75199f16e8de489036d7ea.tar.gz astro-fa84f1a7d2c290479c75199f16e8de489036d7ea.tar.zst astro-fa84f1a7d2c290479c75199f16e8de489036d7ea.zip |
Support streaming inside of slots (#6775)
* Rename renderSlot to renderSlotToString for internal sync usage
* Support streaming inside of slots
* Fix lame lint warning
* Update compiler to fix test
* Up the wait
* Use compiler 1.3.1
* It should be exactly 3
-rw-r--r-- | .changeset/old-bugs-watch.md | 5 | ||||
-rw-r--r-- | packages/astro/package.json | 2 | ||||
-rw-r--r-- | packages/astro/src/core/render/result.ts | 6 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/index.ts | 1 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/component.ts | 6 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/dom.ts | 4 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/index.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/slot.ts | 44 | ||||
-rw-r--r-- | packages/astro/test/astro-slots.test.js | 22 | ||||
-rw-r--r-- | packages/astro/test/fixtures/streaming/src/components/BareComponent.astro | 3 | ||||
-rw-r--r-- | packages/astro/test/fixtures/streaming/src/components/Wait.astro | 6 | ||||
-rw-r--r-- | packages/astro/test/fixtures/streaming/src/pages/slot.astro | 24 | ||||
-rw-r--r-- | packages/astro/test/streaming.test.js | 15 | ||||
-rw-r--r-- | pnpm-lock.yaml | 8 |
14 files changed, 101 insertions, 47 deletions
diff --git a/.changeset/old-bugs-watch.md b/.changeset/old-bugs-watch.md new file mode 100644 index 000000000..f0265a242 --- /dev/null +++ b/.changeset/old-bugs-watch.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Support streaming inside of slots diff --git a/packages/astro/package.json b/packages/astro/package.json index 81d5ca0a0..3b57f3b40 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -106,7 +106,7 @@ "test:e2e:match": "playwright test -g" }, "dependencies": { - "@astrojs/compiler": "^1.3.0", + "@astrojs/compiler": "^1.3.1", "@astrojs/language-server": "^0.28.3", "@astrojs/markdown-remark": "^2.1.3", "@astrojs/telemetry": "^2.1.0", diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index fe88c8b6b..93f3fbaad 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -9,7 +9,7 @@ import type { SSRLoadedRenderer, SSRResult, } from '../../@types/astro'; -import { renderSlot, stringifyChunk, type ComponentSlots } from '../../runtime/server/index.js'; +import { renderSlotToString, stringifyChunk, type ComponentSlots } from '../../runtime/server/index.js'; import { renderJSX } from '../../runtime/server/jsx.js'; import { AstroCookies } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; @@ -105,7 +105,7 @@ class Slots { const expression = getFunctionExpression(component); if (expression) { const slot = () => expression(...args); - return await renderSlot(result, slot).then((res) => (res != null ? String(res) : res)); + return await renderSlotToString(result, slot).then((res) => (res != null ? String(res) : res)); } // JSX if (typeof component === 'function') { @@ -115,7 +115,7 @@ class Slots { } } - const content = await renderSlot(result, this.#slots[name]); + const content = await renderSlotToString(result, this.#slots[name]); const outHTML = stringifyChunk(result, content); return outHTML; diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index ebc234ac2..6f575676b 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -17,6 +17,7 @@ export { renderHTMLElement, renderPage, renderScriptElement, + renderSlotToString, renderSlot, renderStyleElement, renderTemplate as render, diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index 66b0a1d76..4d2b441e1 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -17,7 +17,7 @@ import { } from './astro/index.js'; import { Fragment, Renderer, stringifyChunk } from './common.js'; import { componentIsHTMLElement, renderHTMLElement } from './dom.js'; -import { renderSlot, renderSlots, type ComponentSlots } from './slot.js'; +import { renderSlotToString, renderSlots, type ComponentSlots } from './slot.js'; import { formatList, internalSpreadAttributes, renderElement, voidElementNames } from './util.js'; const rendererAliases = new Map([['solid', 'solid-js']]); @@ -207,7 +207,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr } } else { if (metadata.hydrate === 'only') { - html = await renderSlot(result, slots?.fallback); + html = await renderSlotToString(result, slots?.fallback); } else { ({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call( { result }, @@ -332,7 +332,7 @@ function sanitizeElementName(tag: string) { } async function renderFragmentComponent(result: SSRResult, slots: ComponentSlots = {}) { - const children = await renderSlot(result, slots?.default); + const children = await renderSlotToString(result, slots?.default); if (children == null) { return children; } diff --git a/packages/astro/src/runtime/server/render/dom.ts b/packages/astro/src/runtime/server/render/dom.ts index 352b60436..803f29995 100644 --- a/packages/astro/src/runtime/server/render/dom.ts +++ b/packages/astro/src/runtime/server/render/dom.ts @@ -1,7 +1,7 @@ import type { SSRResult } from '../../../@types/astro'; import { markHTMLString } from '../escape.js'; -import { renderSlot } from './slot.js'; +import { renderSlotToString } from './slot.js'; import { toAttributeString } from './util.js'; export function componentIsHTMLElement(Component: unknown) { @@ -23,7 +23,7 @@ export async function renderHTMLElement( } return markHTMLString( - `<${name}${attrHTML}>${await renderSlot(result, slots?.default)}</${name}>` + `<${name}${attrHTML}>${await renderSlotToString(result, slots?.default)}</${name}>` ); } diff --git a/packages/astro/src/runtime/server/render/index.ts b/packages/astro/src/runtime/server/render/index.ts index 33e3dfe8f..30f85b3ac 100644 --- a/packages/astro/src/runtime/server/render/index.ts +++ b/packages/astro/src/runtime/server/render/index.ts @@ -10,7 +10,7 @@ export { renderComponent, renderComponentToIterable } from './component.js'; export { renderHTMLElement } from './dom.js'; export { maybeRenderHead, renderHead } from './head.js'; export { renderPage } from './page.js'; -export { renderSlot, type ComponentSlots } from './slot.js'; +export { renderSlotToString, renderSlot, type ComponentSlots } from './slot.js'; export { renderScriptElement, renderStyleElement, renderUniqueStylesheet } from './tags.js'; export type { RenderInstruction } from './types'; export { addAttribute, defineScriptVars, voidElementNames } from './util.js'; diff --git a/packages/astro/src/runtime/server/render/slot.ts b/packages/astro/src/runtime/server/render/slot.ts index cd6bc064b..b2ee52d1f 100644 --- a/packages/astro/src/runtime/server/render/slot.ts +++ b/packages/astro/src/runtime/server/render/slot.ts @@ -25,32 +25,40 @@ export function isSlotString(str: string): str is any { return !!(str as any)[slotString]; } -export async function renderSlot( +export async function * renderSlot( result: SSRResult, slotted: ComponentSlotValue | RenderTemplateResult, fallback?: ComponentSlotValue | RenderTemplateResult -): Promise<string> { +): AsyncGenerator<any, void, undefined> { if (slotted) { let iterator = renderChild(typeof slotted === 'function' ? slotted(result) : slotted); - let content = ''; - let instructions: null | RenderInstruction[] = null; - for await (const chunk of iterator) { - if (typeof (chunk as any).type === 'string') { - if (instructions === null) { - instructions = []; - } - instructions.push(chunk); - } else { - content += chunk; - } - } - return markHTMLString(new SlotString(content, instructions)); + yield * iterator; } if (fallback) { - return renderSlot(result, fallback); + yield * renderSlot(result, fallback); + } +} + +export async function renderSlotToString( + result: SSRResult, + slotted: ComponentSlotValue | RenderTemplateResult, + fallback?: ComponentSlotValue | RenderTemplateResult +): Promise<string> { + let content = ''; + let instructions: null | RenderInstruction[] = null; + let iterator = renderSlot(result, slotted, fallback); + for await (const chunk of iterator) { + if (typeof (chunk as any).type === 'string') { + if (instructions === null) { + instructions = []; + } + instructions.push(chunk); + } else { + content += chunk; + } } - return ''; + return markHTMLString(new SlotString(content, instructions)); } interface RenderSlotsResult { @@ -67,7 +75,7 @@ export async function renderSlots( if (slots) { await Promise.all( Object.entries(slots).map(([key, value]) => - renderSlot(result, value).then((output: any) => { + renderSlotToString(result, value).then((output: any) => { if (output.instructions) { if (slotInstructions === null) { slotInstructions = []; diff --git a/packages/astro/test/astro-slots.test.js b/packages/astro/test/astro-slots.test.js index 3c0b2be49..db0d8d338 100644 --- a/packages/astro/test/astro-slots.test.js +++ b/packages/astro/test/astro-slots.test.js @@ -75,9 +75,8 @@ describe('Slots', () => { expect($('#default').children('astro-component')).to.have.lengthOf(1); }); - it('Slots API work on Components', async () => { - // IDs will exist whether the slots are filled or not - { + describe('Slots API work on Components', () => { + it('IDs will exist whether the slots are filled or not', async () => { const html = await fixture.readFile('/slottedapi-default/index.html'); const $ = cheerio.load(html); @@ -85,10 +84,9 @@ describe('Slots', () => { expect($('#b')).to.have.lengthOf(1); expect($('#c')).to.have.lengthOf(1); expect($('#default')).to.have.lengthOf(1); - } + }); - // IDs will not exist because the slots are not filled - { + it('IDs will not exist because the slots are not filled', async () => { const html = await fixture.readFile('/slottedapi-empty/index.html'); const $ = cheerio.load(html); @@ -96,10 +94,9 @@ describe('Slots', () => { expect($('#b')).to.have.lengthOf(0); expect($('#c')).to.have.lengthOf(0); expect($('#default')).to.have.lengthOf(0); - } + }); - // IDs will exist because the slots are filled - { + it('IDs will exist because the slots are filled', async () => { const html = await fixture.readFile('/slottedapi-filled/index.html'); const $ = cheerio.load(html); @@ -108,10 +105,9 @@ describe('Slots', () => { expect($('#c')).to.have.lengthOf(1); expect($('#default')).to.have.lengthOf(0); // the default slot is not filled - } + }); - // Default ID will exist because the default slot is filled - { + it('Default ID will exist because the default slot is filled', async () => { const html = await fixture.readFile('/slottedapi-default-filled/index.html'); const $ = cheerio.load(html); @@ -120,7 +116,7 @@ describe('Slots', () => { expect($('#c')).to.have.lengthOf(0); expect($('#default')).to.have.lengthOf(1); // the default slot is filled - } + }); }); it('Slots.render() API', async () => { diff --git a/packages/astro/test/fixtures/streaming/src/components/BareComponent.astro b/packages/astro/test/fixtures/streaming/src/components/BareComponent.astro new file mode 100644 index 000000000..fb20c0a2b --- /dev/null +++ b/packages/astro/test/fixtures/streaming/src/components/BareComponent.astro @@ -0,0 +1,3 @@ +<section> + <slot /> +</section> diff --git a/packages/astro/test/fixtures/streaming/src/components/Wait.astro b/packages/astro/test/fixtures/streaming/src/components/Wait.astro new file mode 100644 index 000000000..5a2e956be --- /dev/null +++ b/packages/astro/test/fixtures/streaming/src/components/Wait.astro @@ -0,0 +1,6 @@ +--- +import { wait } from '../wait'; +const { ms } = Astro.props; +await wait(ms); +--- +<slot></slot> diff --git a/packages/astro/test/fixtures/streaming/src/pages/slot.astro b/packages/astro/test/fixtures/streaming/src/pages/slot.astro new file mode 100644 index 000000000..ea918cc6f --- /dev/null +++ b/packages/astro/test/fixtures/streaming/src/pages/slot.astro @@ -0,0 +1,24 @@ +--- +import BareComponent from '../components/BareComponent.astro'; +import Wait from '../components/Wait.astro'; +--- + +<html> + <head> + <title>Testing</title> + </head> + <body> + <h1>Testing</h1> + <BareComponent> + <h1>Section title</h1> + <Wait ms={50}> + <p>Section content</p> + </Wait> + <h2>Next section</h2> + <Wait ms={50}> + <p>Section content</p> + </Wait> + <p>Paragraph 3</p> + </BareComponent> + </body> +</html> diff --git a/packages/astro/test/streaming.test.js b/packages/astro/test/streaming.test.js index 47dedac22..c7b835de1 100644 --- a/packages/astro/test/streaming.test.js +++ b/packages/astro/test/streaming.test.js @@ -9,6 +9,8 @@ describe('Streaming', () => { /** @type {import('./test-utils').Fixture} */ let fixture; + let decoder = new TextDecoder(); + before(async () => { fixture = await loadFixture({ root: './fixtures/streaming/', @@ -33,11 +35,21 @@ describe('Streaming', () => { let res = await fixture.fetch('/'); let chunks = []; for await (const bytes of streamAsyncIterator(res.body)) { - let chunk = bytes.toString('utf-8'); + let chunk = decoder.decode(bytes); chunks.push(chunk); } expect(chunks.length).to.be.greaterThan(1); }); + + it('Body of slots is chunked', async () => { + let res = await fixture.fetch('/slot'); + let chunks = []; + for await (const bytes of streamAsyncIterator(res.body)) { + let chunk = decoder.decode(bytes); + chunks.push(chunk); + } + expect(chunks.length).to.equal(3); + }); }); describe('Production', () => { @@ -60,7 +72,6 @@ describe('Streaming', () => { const request = new Request('http://example.com/'); const response = await app.render(request); let chunks = []; - let decoder = new TextDecoder(); for await (const bytes of streamAsyncIterator(response.body)) { let chunk = decoder.decode(bytes); chunks.push(chunk); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74c7a775a..cfc4d3a50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,7 +426,7 @@ importers: packages/astro: specifiers: - '@astrojs/compiler': ^1.3.0 + '@astrojs/compiler': ^1.3.1 '@astrojs/language-server': ^0.28.3 '@astrojs/markdown-remark': ^2.1.3 '@astrojs/telemetry': ^2.1.0 @@ -519,7 +519,7 @@ importers: yargs-parser: ^21.0.1 zod: ^3.17.3 dependencies: - '@astrojs/compiler': 1.3.0 + '@astrojs/compiler': 1.3.1 '@astrojs/language-server': 0.28.3 '@astrojs/markdown-remark': link:../markdown/remark '@astrojs/telemetry': link:../telemetry @@ -4271,8 +4271,8 @@ packages: /@astrojs/compiler/0.31.4: resolution: {integrity: sha512-6bBFeDTtPOn4jZaiD3p0f05MEGQL9pw2Zbfj546oFETNmjJFWO3nzHz6/m+P53calknCvyVzZ5YhoBLIvzn5iw==} - /@astrojs/compiler/1.3.0: - resolution: {integrity: sha512-VxSj3gh/UTB/27rkRCT7SvyGjWtuxUO7Jf7QqDduch7j/gr/uA5P/Q5I/4zIIrZjy2yQAKyKLoox2QI2mM/BSA==} + /@astrojs/compiler/1.3.1: + resolution: {integrity: sha512-xV/3r+Hrfpr4ECfJjRjeaMkJvU73KiOADowHjhkqidfNPVAWPzbqw1KePXuMK1TjzMvoAVE7E163oqfH3lDwSw==} dev: false /@astrojs/language-server/0.28.3: |