Skip to content

Commit

Permalink
feat: rework child rendering to use class (#10624)
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j authored Apr 1, 2024
1 parent ad50784 commit 0be26d8
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 33 deletions.
5 changes: 4 additions & 1 deletion packages/astro/src/runtime/server/render/any.ts
Original file line number Diff line number Diff line change
@@ -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)) {
Expand Down
79 changes: 47 additions & 32 deletions packages/astro/src/runtime/server/render/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand Down

0 comments on commit 0be26d8

Please sign in to comment.