Parallel array rendering (#7136)
This commit is contained in:
parent
6b8d55b096
commit
fea3069360
5 changed files with 33 additions and 16 deletions
5
.changeset/olive-gorillas-cheat.md
Normal file
5
.changeset/olive-gorillas-cheat.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Render arrays of components in parallel
|
|
@ -5,6 +5,7 @@ import {
|
|||
renderAstroTemplateResult,
|
||||
} from './astro/index.js';
|
||||
import { SlotString } from './slot.js';
|
||||
import { bufferIterators } from './util.js';
|
||||
|
||||
export async function* renderChild(child: any): AsyncIterable<any> {
|
||||
child = await child;
|
||||
|
@ -16,8 +17,9 @@ export async function* renderChild(child: any): AsyncIterable<any> {
|
|||
} else if (isHTMLString(child)) {
|
||||
yield child;
|
||||
} else if (Array.isArray(child)) {
|
||||
for (const value of child) {
|
||||
yield markHTMLString(await renderChild(value));
|
||||
const bufferedIterators = bufferIterators(child.map((c) => renderChild(c)));
|
||||
for (const value of bufferedIterators) {
|
||||
yield markHTMLString(await value);
|
||||
}
|
||||
} else if (typeof child === 'function') {
|
||||
// Special: If a child is a function, call it automatically.
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { RenderInstruction } from '../types';
|
|||
import { HTMLBytes, markHTMLString } from '../../escape.js';
|
||||
import { isPromise } from '../../util.js';
|
||||
import { renderChild } from '../any.js';
|
||||
import { EagerAsyncIterableIterator } from '../util.js';
|
||||
import { bufferIterators } from '../util.js';
|
||||
|
||||
const renderTemplateResultSym = Symbol.for('astro.renderTemplateResult');
|
||||
|
||||
|
@ -36,23 +36,15 @@ export class RenderTemplateResult {
|
|||
async *[Symbol.asyncIterator]() {
|
||||
const { htmlParts, expressions } = this;
|
||||
|
||||
let iterables: Array<EagerAsyncIterableIterator> = [];
|
||||
// all async iterators start running in non-buffered mode to avoid useless caching
|
||||
for (let i = 0; i < htmlParts.length; i++) {
|
||||
iterables.push(new EagerAsyncIterableIterator(renderChild(expressions[i])));
|
||||
}
|
||||
// once the execution of the next for loop is suspended due to an async component,
|
||||
// this timeout triggers and we start buffering the other iterators
|
||||
setTimeout(() => {
|
||||
// buffer all iterators that haven't started yet
|
||||
iterables.forEach((it) => !it.isStarted() && it.buffer());
|
||||
}, 0);
|
||||
let iterables = bufferIterators(expressions.map((e) => renderChild(e)));
|
||||
for (let i = 0; i < htmlParts.length; i++) {
|
||||
const html = htmlParts[i];
|
||||
const iterable = iterables[i];
|
||||
|
||||
yield markHTMLString(html);
|
||||
yield* iterable;
|
||||
if (iterable) {
|
||||
yield* iterable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,23 @@ export function renderElement(
|
|||
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will take an array of async iterables and start buffering them eagerly.
|
||||
* To avoid useless buffering, it will only start buffering the next tick, so the
|
||||
* first sync iterables won't be buffered.
|
||||
*/
|
||||
export function bufferIterators<T>(iterators: AsyncIterable<T>[]): AsyncIterable<T>[] {
|
||||
// all async iterators start running in non-buffered mode to avoid useless caching
|
||||
const eagerIterators = iterators.map((it) => new EagerAsyncIterableIterator(it));
|
||||
// once the execution of the next for loop is suspended due to an async component,
|
||||
// this timeout triggers and we start buffering the other iterators
|
||||
setTimeout(() => {
|
||||
// buffer all iterators that haven't started yet
|
||||
eagerIterators.forEach((it) => !it.isStarted() && it.buffer());
|
||||
}, 0);
|
||||
return eagerIterators;
|
||||
}
|
||||
|
||||
// This wrapper around an AsyncIterable can eagerly consume its values, so that
|
||||
// its values are ready to yield out ASAP. This is used for list-like usage of
|
||||
// Astro components, so that we don't have to wait on earlier components to run
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
import Delayed from '../components/Delayed.astro'
|
||||
import Delayed from '../components/Delayed.astro';
|
||||
---
|
||||
|
||||
<Delayed ms={30} />
|
||||
<Delayed ms={20} />
|
||||
<Delayed ms={40} />
|
||||
<Delayed ms={10} />
|
||||
{[<Delayed ms={30} />, <Delayed ms={20} />, <Delayed ms={40} />, <Delayed ms={10} />]}
|
||||
|
|
Loading…
Reference in a new issue