async-hydration
Rich Harris 1 day ago
commit dd7752ca21

@ -39,10 +39,7 @@ function print_error(renderer, message) {
// eslint-disable-next-line no-console
console.error(message);
renderer.child(
(renderer) => renderer.push(`<script>console.error(${JSON.stringify(message)})</script>`),
'head'
);
renderer.head((r) => r.push(`<script>console.error(${JSON.stringify(message)})</script>`));
}
/**

@ -69,11 +69,11 @@ export function render(component, options = {}) {
* @returns {void}
*/
export function head(renderer, fn) {
renderer.child((renderer) => {
renderer.head((renderer) => {
renderer.push(BLOCK_OPEN);
renderer.child(fn);
renderer.push(BLOCK_CLOSE);
}, 'head');
});
}
/**

@ -81,23 +81,33 @@ export class Renderer {
/**
* @param {SSRState} global
* @param {Renderer | undefined} [parent]
* @param {RendererType} [type]
*/
constructor(global, parent, type) {
constructor(global, parent) {
this.#parent = parent;
this.global = global;
this.local = parent ? { ...parent.local } : { select_value: undefined };
this.#parent = parent;
this.type = type ?? parent?.type ?? 'body';
this.type = parent ? parent.type : 'body';
}
/**
* @param {(renderer: Renderer) => void} fn
*/
head(fn) {
const head = new Renderer(this.global, this);
head.type = 'head';
this.#out.push(head);
head.child(fn);
}
/**
* Create a child renderer. The child renderer inherits the state from the parent,
* but has its own content.
* @param {(renderer: Renderer) => MaybePromise<void>} fn
* @param {RendererType} [type]
*/
child(fn, type) {
const child = new Renderer(this.global, this, type);
child(fn) {
const child = new Renderer(this.global, this);
const parent = ssr_context;
@ -189,7 +199,7 @@ export class Renderer {
* @deprecated this is needed for legacy component bindings
*/
copy() {
const copy = new Renderer(this.global, this.#parent, this.type);
const copy = new Renderer(this.global, this.#parent);
copy.#out = this.#out.map((item) => (item instanceof Renderer ? item.copy() : item));
copy.promises = this.promises;
return copy;
@ -462,7 +472,8 @@ export class Renderer {
static #push_accumulated_content(tree, accumulated_content) {
for (const [type, content] of Object.entries(accumulated_content)) {
if (!content) continue;
const child = new Renderer(tree.global, tree, /** @type {RendererType} */ (type));
const child = new Renderer(tree.global, tree);
child.type = /** @type {RendererType} */ (type);
child.push(content);
tree.#out.push(child);
}

@ -20,9 +20,9 @@ test('collects synchronous body content by default', () => {
test('child type switches content area (head vs body)', () => {
const component = (renderer: Renderer) => {
renderer.push('a');
renderer.child(($$renderer) => {
renderer.head(($$renderer) => {
$$renderer.push('<title>T</title>');
}, 'head');
});
renderer.push('b');
};
@ -33,12 +33,12 @@ test('child type switches content area (head vs body)', () => {
test('child inherits parent type when not specified', () => {
const component = (renderer: Renderer) => {
renderer.child((renderer) => {
renderer.head((renderer) => {
renderer.push('<meta name="x"/>');
renderer.child((renderer) => {
renderer.push('<style>/* css */</style>');
});
}, 'head');
});
};
const { head, body } = Renderer.render(component as unknown as Component);
@ -118,7 +118,9 @@ test('local state is shallow-copied to children', () => {
});
test('subsume replaces tree content and state from other', () => {
const a = new Renderer(new SSRState('async'), undefined, 'head');
const a = new Renderer(new SSRState('async'));
a.type = 'head';
a.push('<meta />');
a.local.select_value = 'A';
@ -140,7 +142,9 @@ test('subsume replaces tree content and state from other', () => {
});
test('subsume refuses to switch modes', () => {
const a = new Renderer(new SSRState('sync'), undefined, 'head');
const a = new Renderer(new SSRState('sync'));
a.type = 'head';
a.push('<meta />');
a.local.select_value = 'A';
@ -273,12 +277,12 @@ describe('async', () => {
test('push async functions work with head content type', async () => {
const component = (renderer: Renderer) => {
renderer.child(($$renderer) => {
renderer.head(($$renderer) => {
$$renderer.push(async () => {
await Promise.resolve();
return '<title>Async Title</title>';
});
}, 'head');
});
};
const { head, body } = await Renderer.render(component as unknown as Component);

@ -1,5 +1,3 @@
import { test } from '../../test';
export default test({
props: { adjective: 'custom' }
});
export default test({});

Loading…
Cancel
Save