pull/16797/head
Rich Harris 2 days ago
commit dd7752ca21

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

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

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

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

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

Loading…
Cancel
Save