0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00
This commit is contained in:
Matt Kane 2024-10-03 09:12:20 +01:00
parent 8f064fa5b9
commit a1dc4948fc
3 changed files with 55 additions and 10 deletions

View file

@ -125,7 +125,7 @@ export async function renderToReadableStream(
}
// Queue error on next microtask to flush the remaining chunks written synchronously
setTimeout(() => controller.error(e), 0);
queueMicrotask(() => controller.error(e));
}
})();
},
@ -229,9 +229,12 @@ export async function renderToAsyncIterable(
// The `next` is an object `{ promise, resolve, reject }` that we use to wait
// for chunks to be pushed into the buffer.
let next: ReturnType<typeof promiseWithResolvers<void>> | null = null;
const buffer: Uint8Array[] = []; // []Uint8Array
const buffer: Uint8Array[] = [];
let renderingComplete = false;
const BATCH_SIZE = 1024 * 1;
let currentBatchSize = 0;
const iterator: AsyncIterator<Uint8Array> = {
async next() {
if (result.cancelled) return { done: true, value: undefined };
@ -271,8 +274,9 @@ export async function renderToAsyncIterable(
offset += item.length;
}
// Empty the array. We do this so that we can reuse the same array.
// Empty the buffer and reset the batch size
buffer.length = 0;
currentBatchSize = 0; // **Reset batch size after sending**
const returnValue = {
// The iterator is done when rendering has finished
@ -297,7 +301,9 @@ export async function renderToAsyncIterable(
renderedFirstPageChunk = true;
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
const doctype = result.compressHTML ? '<!DOCTYPE html>' : '<!DOCTYPE html>\n';
buffer.push(encoder.encode(doctype));
const doctypeBytes = encoder.encode(doctype);
buffer.push(doctypeBytes);
currentBatchSize += doctypeBytes.length;
}
}
if (chunk instanceof Response) {
@ -310,7 +316,13 @@ export async function renderToAsyncIterable(
// Push the chunks into the buffer and resolve the promise so that next()
// will run.
buffer.push(bytes);
next?.resolve();
currentBatchSize += bytes.length;
// Check if batch size threshold is reached
if (currentBatchSize >= BATCH_SIZE) {
next?.resolve();
// Note: Do not reset currentBatchSize here; it will be reset in `next()`
}
} else if (buffer.length > 0) {
next?.resolve();
}

View file

@ -7,6 +7,28 @@ import { renderToAsyncIterable, renderToReadableStream, renderToString } from '.
import { encoder } from './common.js';
import { isDeno, isNode } from './util.js';
function readableStreamFromAsyncIterable(
asyncIterable: AsyncIterable<Uint8Array>,
) {
const iterator = asyncIterable[Symbol.asyncIterator]();
return new ReadableStream(
{
async pull(controller) {
try {
const { value, done } = await iterator.next();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
} catch (err) {
controller.error(err);
}
}
},
);
}
export async function renderPage(
result: SSRResult,
componentFactory: AstroComponentFactory | NonAstroPageComponent,
@ -59,9 +81,7 @@ export async function renderPage(
true,
route,
);
// Node.js allows passing in an AsyncIterable to the Response constructor.
// This is non-standard so using `any` here to preserve types everywhere else.
body = nodeBody as any;
body = nodeBody instanceof Response ? nodeBody : readableStreamFromAsyncIterable(nodeBody);
} else {
body = await renderToReadableStream(result, componentFactory, props, children, true, route);
}

View file

@ -232,9 +232,22 @@ export type PromiseWithResolvers<T> = {
reject: (reason?: any) => void;
};
// This is an implementation of Promise.withResolvers(), which we can't yet rely on.
// We can remove this once the native function is available in Node.js
interface PromiseConstructorWithResolvers<T> extends PromiseConstructor {
withResolvers: () => PromiseWithResolvers<T>;
}
function hasWithResolvers<T>(
promiseConstructor: PromiseConstructor | PromiseConstructorWithResolvers<T>,
): promiseConstructor is PromiseConstructorWithResolvers<T> {
return 'withResolvers' in promiseConstructor;
}
// This is an implementation of Promise.withResolvers(), which was added in Node v22
// We can remove this once we drop support for Node <22.0.0
export function promiseWithResolvers<T = any>(): PromiseWithResolvers<T> {
if (hasWithResolvers<T>(Promise)) {
return Promise.withResolvers();
}
let resolve: any, reject: any;
const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve;