astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
56 lines (55 loc) • 2.75 kB
JavaScript
/**
* Runs all registered propagators and collects the head HTML they emit.
*
* Components with head content are discovered as we go. Initializing one
* propagator can register more of them: a component marked `in-tree` renders
* its children, and one of those children may be a `self` component that emits
* styles. Slots add a second way to find them — a slot whose markup contains an
* `await` only reaches the components after that `await` once it resolves, so
* the pending slot pre-renders are fully drained before moving on to the next
* propagator.
*
* A single pass over the live `Set` reaches every late registration: a `Set`
* iterator visits entries added during iteration (in insertion order), and
* because slots are drained before the iterator advances, nothing can register
* a propagator after the iterator has reported `done`.
*
* Set `awaitPendingSlots` to `false` when collecting from inside a render that
* may itself be one of the pending slot pre-renders (a server island rendered
* through `renderComponentToString`). Awaiting them there would wait on the
* caller's own enclosing slot and never resolve (#18156).
*
* @example
* If a layout initializes and discovers a nested component that also emits
* `<link rel="stylesheet">`, both head chunks are collected before flush.
*/
export async function collectPropagatedHeadParts(input) {
const collectedHeadParts = [];
// Populated (only on propagation routes) by eager async slot pre-renders.
const pendingSlotEvaluations = input.awaitPendingSlots === false ? [] : input.result._metadata?.pendingSlotEvaluations ?? [];
// Resolving a pending slot pre-render runs the slot markup past its
// `await`s, registering any propagators inside — and possibly queueing
// deeper slot pre-renders, hence the loop.
const drainPendingSlots = async () => {
while (pendingSlotEvaluations.length > 0) {
const batch = pendingSlotEvaluations.splice(0, pendingSlotEvaluations.length);
await Promise.all(batch);
}
};
// Keep this as a single pass over the live `Set` so collection stays O(N).
// Draining pending slots before the iterator advances lets it discover
// propagators registered by async slot evaluations.
await drainPendingSlots();
for (const propagator of input.propagators) {
const returnValue = await propagator.init(input.result);
// Only collect explicit head-bearing return values.
if (input.isHeadAndContent(returnValue) && returnValue.head) {
collectedHeadParts.push(returnValue.head);
}
// `init()` may have queued new slot pre-renders. Drain them before the
// iterator advances so any propagators they register are appended while
// the iterator is still active.
await drainPendingSlots();
}
return collectedHeadParts;
}