diff options
author | 2024-04-01 14:25:20 +0100 | |
---|---|---|
committer | 2024-04-01 21:25:20 +0800 | |
commit | 0be26d8b10fb19efaa9c1d2f88a4950d9550994a (patch) | |
tree | 8ef231c974d776dee363dbd93dba8f03a9fea6d2 | |
parent | ad50784adc6f262fc563999e97df3a5dc9087c88 (diff) | |
download | astro-0be26d8b10fb19efaa9c1d2f88a4950d9550994a.tar.gz astro-0be26d8b10fb19efaa9c1d2f88a4950d9550994a.tar.zst astro-0be26d8b10fb19efaa9c1d2f88a4950d9550994a.zip |
feat: rework child rendering to use class (#10624)
-rw-r--r-- | packages/astro/src/runtime/server/render/any.ts | 5 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/util.ts | 79 |
2 files changed, 51 insertions, 33 deletions
diff --git a/packages/astro/src/runtime/server/render/any.ts b/packages/astro/src/runtime/server/render/any.ts index 20de2d732..e7e9f0b56 100644 --- a/packages/astro/src/runtime/server/render/any.ts +++ b/packages/astro/src/runtime/server/render/any.ts @@ -1,11 +1,14 @@ import { escapeHTML, isHTMLString, markHTMLString } from '../escape.js'; +import { isPromise } from '../util.js'; import { isAstroComponentInstance, isRenderTemplateResult } from './astro/index.js'; import { type RenderDestination, isRenderInstance } from './common.js'; import { SlotString } from './slot.js'; import { renderToBufferDestination } from './util.js'; export async function renderChild(destination: RenderDestination, child: any) { - child = await child; + if (isPromise(child)) { + child = await child; + } if (child instanceof SlotString) { destination.write(child); } else if (isHTMLString(child)) { diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts index 61caff6cc..5e1fff9fc 100644 --- a/packages/astro/src/runtime/server/render/util.ts +++ b/packages/astro/src/runtime/server/render/util.ts @@ -149,6 +149,51 @@ export function renderElement( return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`; } +const noop = () => {}; + +/** + * Renders into a buffer until `renderToFinalDestination` is called (which + * flushes the buffer) + */ +class BufferedRenderer implements RenderDestination { + private chunks: RenderDestinationChunk[] = []; + private renderPromise: Promise<void> | void; + private destination?: RenderDestination; + + public constructor(bufferRenderFunction: RenderFunction) { + this.renderPromise = bufferRenderFunction(this); + // Catch here in case it throws before `renderToFinalDestination` is called, + // to prevent an unhandled rejection. + Promise.resolve(this.renderPromise).catch(noop); + } + + public write(chunk: RenderDestinationChunk): void { + if (this.destination) { + this.destination.write(chunk); + } else { + this.chunks.push(chunk); + } + } + + public async renderToFinalDestination(destination: RenderDestination) { + // Write the buffered chunks to the real destination + for (const chunk of this.chunks) { + destination.write(chunk); + } + + // NOTE: We don't empty `this.chunks` after it's written as benchmarks show + // that it causes poorer performance, likely due to forced memory re-allocation, + // instead of letting the garbage collector handle it automatically. + // (Unsure how this affects on limited memory machines) + + // Re-assign the real destination so `instance.render` will continue and write to the new destination + this.destination = destination; + + // Wait for render to finish entirely + await this.renderPromise; + } +} + /** * Executes the `bufferRenderFunction` to prerender it into a buffer destination, and return a promise * with an object containing the `renderToFinalDestination` function to flush the buffer to the final @@ -171,38 +216,8 @@ export function renderElement( export function renderToBufferDestination(bufferRenderFunction: RenderFunction): { renderToFinalDestination: RenderFunction; } { - // Keep chunks in memory - const bufferChunks: RenderDestinationChunk[] = []; - const bufferDestination: RenderDestination = { - write: (chunk) => bufferChunks.push(chunk), - }; - - // Don't await for the render to finish to not block streaming - const renderPromise = bufferRenderFunction(bufferDestination); - // Catch here in case it throws before `renderToFinalDestination` is called, - // to prevent an unhandled rejection. - Promise.resolve(renderPromise).catch(() => {}); - - // Return a closure that writes the buffered chunk - return { - async renderToFinalDestination(destination) { - // Write the buffered chunks to the real destination - for (const chunk of bufferChunks) { - destination.write(chunk); - } - - // NOTE: We don't empty `bufferChunks` after it's written as benchmarks show - // that it causes poorer performance, likely due to forced memory re-allocation, - // instead of letting the garbage collector handle it automatically. - // (Unsure how this affects on limited memory machines) - - // Re-assign the real destination so `instance.render` will continue and write to the new destination - bufferDestination.write = (chunk) => destination.write(chunk); - - // Wait for render to finish entirely - await renderPromise; - }, - }; + const renderer = new BufferedRenderer(bufferRenderFunction); + return renderer; } export const isNode = |