astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
236 lines (235 loc) • 7.13 kB
JavaScript
import { AstroError, AstroErrorData } from "../../../../core/errors/index.js";
import { isPromise } from "../../util.js";
import { chunkToByteArray, chunkToString, encoder } from "../common.js";
import { promiseWithResolvers } from "../util.js";
import { isHeadAndContent } from "./head-and-content.js";
import { isRenderTemplateResult } from "./render-template.js";
const DOCTYPE_EXP = /
{
try {
await templateResult.render(destination);
controller.close();
} catch (e) {
if (AstroError.is(e) && !e.loc) {
e.setLocation({
file: route?.component
});
}
setTimeout(() => controller.error(e), 0);
}
})();
},
cancel() {
result.cancelled = true;
}
});
}
async function callComponentAsTemplateResultOrResponse(result, componentFactory, props, children, route) {
const factoryResult = await componentFactory(result, props, children);
if (factoryResult instanceof Response) {
return factoryResult;
} else if (isHeadAndContent(factoryResult)) {
if (!isRenderTemplateResult(factoryResult.content)) {
throw new AstroError({
...AstroErrorData.OnlyResponseCanBeReturned,
message: AstroErrorData.OnlyResponseCanBeReturned.message(
route?.route,
typeof factoryResult
),
location: {
file: route?.component
}
});
}
return factoryResult.content;
} else if (!isRenderTemplateResult(factoryResult)) {
throw new AstroError({
...AstroErrorData.OnlyResponseCanBeReturned,
message: AstroErrorData.OnlyResponseCanBeReturned.message(route?.route, typeof factoryResult),
location: {
file: route?.component
}
});
}
return factoryResult;
}
async function bufferHeadContent(result) {
const iterator = result._metadata.propagators.values();
while (true) {
const { value, done } = iterator.next();
if (done) {
break;
}
const returnValue = await value.init(result);
if (isHeadAndContent(returnValue)) {
result._metadata.extraHead.push(returnValue.head);
}
}
}
async function renderToAsyncIterable(result, componentFactory, props, children, isPage = false, route) {
const templateResult = await callComponentAsTemplateResultOrResponse(
result,
componentFactory,
props,
children,
route
);
if (templateResult instanceof Response) return templateResult;
let renderedFirstPageChunk = false;
if (isPage) {
await bufferHeadContent(result);
}
let error = null;
let next = null;
const buffer = [];
let renderingComplete = false;
const iterator = {
async next() {
if (result.cancelled) return { done: true, value: void 0 };
if (next !== null) {
await next.promise;
} else if (!renderingComplete && !buffer.length) {
next = promiseWithResolvers();
await next.promise;
}
if (!renderingComplete) {
next = promiseWithResolvers();
}
if (error) {
throw error;
}
let length = 0;
for (let i = 0, len = buffer.length; i < len; i++) {
length += buffer[i].length;
}
let mergedArray = new Uint8Array(length);
let offset = 0;
for (let i = 0, len = buffer.length; i < len; i++) {
const item = buffer[i];
mergedArray.set(item, offset);
offset += item.length;
}
buffer.length = 0;
const returnValue = {
// The iterator is done when rendering has finished
// and there are no more chunks to return.
done: length === 0 && renderingComplete,
value: mergedArray
};
return returnValue;
},
async return() {
result.cancelled = true;
return { done: true, value: void 0 };
}
};
const destination = {
write(chunk) {
if (isPage && !renderedFirstPageChunk) {
renderedFirstPageChunk = true;
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
const doctype = result.compressHTML ? " " : " \n";
buffer.push(encoder.encode(doctype));
}
}
if (chunk instanceof Response) {
throw new AstroError(AstroErrorData.ResponseSentError);
}
const bytes = chunkToByteArray(result, chunk);
if (bytes.length > 0) {
buffer.push(bytes);
next?.resolve();
} else if (buffer.length > 0) {
next?.resolve();
}
}
};
const renderResult = toPromise(() => templateResult.render(destination));
renderResult.catch((err) => {
error = err;
}).finally(() => {
renderingComplete = true;
next?.resolve();
});
return {
[Symbol.asyncIterator]() {
return iterator;
}
};
}
function toPromise(fn) {
try {
const result = fn();
return isPromise(result) ? result : Promise.resolve(result);
} catch (err) {
return Promise.reject(err);
}
}
export {
renderToAsyncIterable,
renderToReadableStream,
renderToString
};