chore: Rename Payload to Renderer (#16786)

pull/16785/head
Elliott Johnson 2 days ago committed by GitHub
parent fc35d9fab8
commit 6798efacb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -40,7 +40,7 @@ import { TitleElement } from './visitors/TitleElement.js';
import { UpdateExpression } from './visitors/UpdateExpression.js'; import { UpdateExpression } from './visitors/UpdateExpression.js';
import { VariableDeclaration } from './visitors/VariableDeclaration.js'; import { VariableDeclaration } from './visitors/VariableDeclaration.js';
import { SvelteBoundary } from './visitors/SvelteBoundary.js'; import { SvelteBoundary } from './visitors/SvelteBoundary.js';
import { call_child_payload, call_component_payload } from './visitors/shared/utils.js'; import { call_child_renderer, call_component_renderer } from './visitors/shared/utils.js';
/** @type {Visitors} */ /** @type {Visitors} */
const global_visitors = { const global_visitors = {
@ -188,21 +188,21 @@ export function server_component(analysis, options) {
template.body = [ template.body = [
...snippets, ...snippets,
b.let('$$settled', b.true), b.let('$$settled', b.true),
b.let('$$inner_payload'), b.let('$$inner_renderer'),
b.function_declaration( b.function_declaration(
b.id('$$render_inner'), b.id('$$render_inner'),
[b.id('$$payload')], [b.id(' $$renderer')],
b.block(/** @type {Statement[]} */ (rest)) b.block(/** @type {Statement[]} */ (rest))
), ),
b.do_while( b.do_while(
b.unary('!', b.id('$$settled')), b.unary('!', b.id('$$settled')),
b.block([ b.block([
b.stmt(b.assignment('=', b.id('$$settled'), b.true)), b.stmt(b.assignment('=', b.id('$$settled'), b.true)),
b.stmt(b.assignment('=', b.id('$$inner_payload'), b.call('$$payload.copy'))), b.stmt(b.assignment('=', b.id('$$inner_renderer'), b.call(' $$renderer.copy'))),
b.stmt(b.call('$$render_inner', b.id('$$inner_payload'))) b.stmt(b.call('$$render_inner', b.id('$$inner_renderer')))
]) ])
), ),
b.stmt(b.call('$$payload.subsume', b.id('$$inner_payload'))) b.stmt(b.call(' $$renderer.subsume', b.id('$$inner_renderer')))
]; ];
} }
@ -244,7 +244,7 @@ export function server_component(analysis, options) {
]); ]);
if (analysis.instance.has_await) { if (analysis.instance.has_await) {
component_block = b.block([call_child_payload(component_block, true)]); component_block = b.block([call_child_renderer(component_block, true)]);
} }
// trick esrap into including comments // trick esrap into including comments
@ -253,7 +253,7 @@ export function server_component(analysis, options) {
if (analysis.props_id) { if (analysis.props_id) {
// need to be placed on first line of the component for hydration // need to be placed on first line of the component for hydration
component_block.body.unshift( component_block.body.unshift(
b.const(analysis.props_id, b.call('$.props_id', b.id('$$payload'))) b.const(analysis.props_id, b.call('$.props_id', b.id(' $$renderer')))
); );
} }
@ -261,7 +261,7 @@ export function server_component(analysis, options) {
if (should_inject_context) { if (should_inject_context) {
component_block = b.block([ component_block = b.block([
call_component_payload(component_block, dev && b.id(component_name)) call_component_renderer(component_block, dev && b.id(component_name))
]); ]);
} }
@ -301,7 +301,7 @@ export function server_component(analysis, options) {
const code = b.literal(render_stylesheet(analysis.source, analysis, options).code); const code = b.literal(render_stylesheet(analysis.source, analysis, options).code);
body.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)]))); body.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)])));
component_block.body.unshift(b.stmt(b.call('$$payload.global.css.add', b.id('$$css')))); component_block.body.unshift(b.stmt(b.call(' $$renderer.global.css.add', b.id('$$css'))));
} }
let should_inject_props = let should_inject_props =
@ -315,7 +315,7 @@ export function server_component(analysis, options) {
const component_function = b.function_declaration( const component_function = b.function_declaration(
b.id(analysis.name), b.id(analysis.name),
should_inject_props ? [b.id('$$payload'), b.id('$$props')] : [b.id('$$payload')], should_inject_props ? [b.id(' $$renderer'), b.id('$$props')] : [b.id(' $$renderer')],
component_block component_block
); );

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */ /** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { block_close, call_child_payload } from './shared/utils.js'; import { block_close, call_child_renderer } from './shared/utils.js';
/** /**
* @param {AST.AwaitBlock} node * @param {AST.AwaitBlock} node
@ -13,7 +13,7 @@ export function AwaitBlock(node, context) {
let statement = b.stmt( let statement = b.stmt(
b.call( b.call(
'$.await', '$.await',
b.id('$$payload'), b.id(' $$renderer'),
/** @type {Expression} */ (context.visit(node.expression)), /** @type {Expression} */ (context.visit(node.expression)),
b.thunk( b.thunk(
node.pending ? /** @type {BlockStatement} */ (context.visit(node.pending)) : b.block([]) node.pending ? /** @type {BlockStatement} */ (context.visit(node.pending)) : b.block([])
@ -26,7 +26,7 @@ export function AwaitBlock(node, context) {
); );
if (node.metadata.expression.has_await) { if (node.metadata.expression.has_await) {
statement = call_child_payload(b.block([statement]), true); statement = call_child_renderer(b.block([statement]), true);
} }
context.state.template.push(statement, block_close); context.state.template.push(statement, block_close);

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */ /** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { block_close, block_open, block_open_else, call_child_payload } from './shared/utils.js'; import { block_close, block_open, block_open_else, call_child_renderer } from './shared/utils.js';
/** /**
* @param {AST.EachBlock} node * @param {AST.EachBlock} node
@ -45,11 +45,11 @@ export function EachBlock(node, context) {
); );
if (node.fallback) { if (node.fallback) {
const open = b.stmt(b.call(b.id('$$payload.push'), block_open)); const open = b.stmt(b.call(b.id(' $$renderer.push'), block_open));
const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback)); const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback));
fallback.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open_else))); fallback.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open_else)));
block.body.push( block.body.push(
b.if( b.if(
@ -64,7 +64,7 @@ export function EachBlock(node, context) {
} }
if (node.metadata.expression.has_await) { if (node.metadata.expression.has_await) {
state.template.push(call_child_payload(block, true), block_close); state.template.push(call_child_renderer(block, true), block_close);
} else { } else {
state.template.push(...block.body, block_close); state.template.push(...block.body, block_close);
} }

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */ /** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { block_close, block_open, block_open_else, call_child_payload } from './shared/utils.js'; import { block_close, block_open, block_open_else, call_child_renderer } from './shared/utils.js';
/** /**
* @param {AST.IfBlock} node * @param {AST.IfBlock} node
@ -16,15 +16,15 @@ export function IfBlock(node, context) {
? /** @type {BlockStatement} */ (context.visit(node.alternate)) ? /** @type {BlockStatement} */ (context.visit(node.alternate))
: b.block([]); : b.block([]);
consequent.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open))); consequent.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open)));
alternate.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open_else))); alternate.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open_else)));
/** @type {Statement} */ /** @type {Statement} */
let statement = b.if(test, consequent, alternate); let statement = b.if(test, consequent, alternate);
if (node.metadata.expression.has_await) { if (node.metadata.expression.has_await) {
statement = call_child_payload(b.block([statement]), true); statement = call_child_renderer(b.block([statement]), true);
} }
context.state.template.push(statement, block_close); context.state.template.push(statement, block_close);

@ -12,7 +12,7 @@ import {
process_children, process_children,
build_template, build_template,
build_attribute_value, build_attribute_value,
call_child_payload call_child_renderer
} from './shared/utils.js'; } from './shared/utils.js';
/** /**
@ -68,7 +68,7 @@ export function RegularElement(node, context) {
b.stmt( b.stmt(
b.call( b.call(
'$.push_element', '$.push_element',
b.id('$$payload'), b.id(' $$renderer'),
b.literal(node.name), b.literal(node.name),
b.literal(location.line), b.literal(location.line),
b.literal(location.column) b.literal(location.column)
@ -97,7 +97,7 @@ export function RegularElement(node, context) {
b.stmt( b.stmt(
b.assignment( b.assignment(
'=', '=',
b.id('$$payload.local.select_value'), b.id(' $$renderer.local.select_value'),
b.member( b.member(
build_spread_object( build_spread_object(
node, node,
@ -125,7 +125,7 @@ export function RegularElement(node, context) {
); );
} }
const left = b.id('$$payload.local.select_value'); const left = b.id(' $$renderer.local.select_value');
if (value.type === 'Attribute') { if (value.type === 'Attribute') {
state.template.push( state.template.push(
b.stmt(b.assignment('=', left, build_attribute_value(value.value, context))) b.stmt(b.assignment('=', left, build_attribute_value(value.value, context)))
@ -160,7 +160,7 @@ export function RegularElement(node, context) {
b.stmt( b.stmt(
b.call( b.call(
'$.simple_valueless_option', '$.simple_valueless_option',
b.id('$$payload'), b.id(' $$renderer'),
b.thunk( b.thunk(
node.metadata.synthetic_value_node.expression, node.metadata.synthetic_value_node.expression,
node.metadata.synthetic_value_node.metadata.expression.has_await node.metadata.synthetic_value_node.metadata.expression.has_await
@ -175,9 +175,9 @@ export function RegularElement(node, context) {
b.stmt( b.stmt(
b.call( b.call(
'$.valueless_option', '$.valueless_option',
b.id('$$payload'), b.id(' $$renderer'),
b.arrow( b.arrow(
[b.id('$$payload')], [b.id(' $$renderer')],
b.block([...inner_state.init, ...build_template(inner_state.template)]) b.block([...inner_state.init, ...build_template(inner_state.template)])
) )
) )
@ -216,7 +216,7 @@ export function RegularElement(node, context) {
// element hadn't resolved prior to hitting the second element. // element hadn't resolved prior to hitting the second element.
const elements = state.template.splice(template_start, Infinity); const elements = state.template.splice(template_start, Infinity);
state.template.push( state.template.push(
call_child_payload(b.block(build_template(elements)), select_with_value_async) call_child_renderer(b.block(build_template(elements)), select_with_value_async)
); );
} }

@ -23,7 +23,7 @@ export function RenderTag(node, context) {
b.stmt( b.stmt(
(node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)(
snippet_function, snippet_function,
b.id('$$payload'), b.id(' $$renderer'),
...snippet_args ...snippet_args
) )
) )

@ -6,7 +6,7 @@ import {
empty_comment, empty_comment,
build_attribute_value, build_attribute_value,
PromiseOptimiser, PromiseOptimiser,
call_child_payload call_child_renderer
} from './shared/utils.js'; } from './shared/utils.js';
/** /**
@ -57,7 +57,7 @@ export function SlotElement(node, context) {
const slot = b.call( const slot = b.call(
'$.slot', '$.slot',
b.id('$$payload'), b.id(' $$renderer'),
b.id('$$props'), b.id('$$props'),
name, name,
props_expression, props_expression,
@ -66,7 +66,7 @@ export function SlotElement(node, context) {
const statement = const statement =
optimiser.expressions.length > 0 optimiser.expressions.length > 0
? call_child_payload(b.block([optimiser.apply(), b.stmt(slot)]), true) ? call_child_renderer(b.block([optimiser.apply(), b.stmt(slot)]), true)
: b.stmt(slot); : b.stmt(slot);
context.state.template.push(empty_comment, statement, empty_comment); context.state.template.push(empty_comment, statement, empty_comment);

@ -11,7 +11,7 @@ import * as b from '#compiler/builders';
export function SnippetBlock(node, context) { export function SnippetBlock(node, context) {
let fn = b.function_declaration( let fn = b.function_declaration(
node.expression, node.expression,
[b.id('$$payload'), ...node.parameters], [b.id(' $$renderer'), ...node.parameters],
/** @type {BlockStatement} */ (context.visit(node.body)) /** @type {BlockStatement} */ (context.visit(node.body))
); );
@ -21,7 +21,7 @@ export function SnippetBlock(node, context) {
const statements = node.metadata.can_hoist ? context.state.hoisted : context.state.init; const statements = node.metadata.can_hoist ? context.state.hoisted : context.state.init;
if (dev) { if (dev) {
fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id('$$payload')))); fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id(' $$renderer'))));
statements.push(b.stmt(b.call('$.prevent_snippet_stringification', fn.id))); statements.push(b.stmt(b.call('$.prevent_snippet_stringification', fn.id)));
} }

@ -24,7 +24,7 @@ export function SvelteBoundary(node, context) {
const pending = pending_attribute const pending = pending_attribute
? b.call( ? b.call(
build_attribute_value(pending_attribute.value, context, false, true), build_attribute_value(pending_attribute.value, context, false, true),
b.id('$$payload') b.id(' $$renderer')
) )
: /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); : /** @type {BlockStatement} */ (context.visit(pending_snippet.body));

@ -45,7 +45,7 @@ export function SvelteElement(node, context) {
b.stmt( b.stmt(
b.call( b.call(
'$.push_element', '$.push_element',
b.id('$$payload'), b.id(' $$renderer'),
tag, tag,
b.literal(location.line), b.literal(location.line),
b.literal(location.column) b.literal(location.column)
@ -61,7 +61,7 @@ export function SvelteElement(node, context) {
b.stmt( b.stmt(
b.call( b.call(
'$.element', '$.element',
b.id('$$payload'), b.id(' $$renderer'),
tag, tag,
attributes.body.length > 0 && b.thunk(attributes), attributes.body.length > 0 && b.thunk(attributes),
children.body.length > 0 && b.thunk(children) children.body.length > 0 && b.thunk(children)

@ -11,6 +11,6 @@ export function SvelteHead(node, context) {
const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); const block = /** @type {BlockStatement} */ (context.visit(node.fragment));
context.state.template.push( context.state.template.push(
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], block))) b.stmt(b.call('$.head', b.id(' $$renderer'), b.arrow([b.id(' $$renderer')], block)))
); );
} }

@ -14,6 +14,6 @@ export function TitleElement(node, context) {
template.push(b.literal('</title>')); template.push(b.literal('</title>'));
context.state.init.push( context.state.init.push(
b.stmt(b.call('$.build_title', b.id('$$payload'), b.thunk(b.block(build_template(template))))) b.stmt(b.call('$.build_title', b.id(' $$renderer'), b.thunk(b.block(build_template(template)))))
); );
} }

@ -4,7 +4,7 @@
import { import {
empty_comment, empty_comment,
build_attribute_value, build_attribute_value,
call_child_payload, call_child_renderer,
PromiseOptimiser PromiseOptimiser
} from './utils.js'; } from './utils.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
@ -215,7 +215,7 @@ export function build_inline_component(node, expression, context) {
if (block.body.length === 0) continue; if (block.body.length === 0) continue;
/** @type {Pattern[]} */ /** @type {Pattern[]} */
const params = [b.id('$$payload')]; const params = [b.id(' $$renderer')];
if (lets[slot_name].length > 0) { if (lets[slot_name].length > 0) {
const pattern = b.object_pattern( const pattern = b.object_pattern(
@ -292,7 +292,7 @@ export function build_inline_component(node, expression, context) {
let statement = b.stmt( let statement = b.stmt(
(node.type === 'SvelteComponent' ? b.maybe_call : b.call)( (node.type === 'SvelteComponent' ? b.maybe_call : b.call)(
expression, expression,
b.id('$$payload'), b.id(' $$renderer'),
props_expression props_expression
) )
); );
@ -308,7 +308,7 @@ export function build_inline_component(node, expression, context) {
statement = b.stmt( statement = b.stmt(
b.call( b.call(
'$.css_props', '$.css_props',
b.id('$$payload'), b.id(' $$renderer'),
b.literal(context.state.namespace === 'svg' ? false : true), b.literal(context.state.namespace === 'svg' ? false : true),
b.object(custom_css_props), b.object(custom_css_props),
b.thunk(b.block([statement])), b.thunk(b.block([statement])),
@ -318,7 +318,7 @@ export function build_inline_component(node, expression, context) {
} }
if (optimiser.expressions.length > 0) { if (optimiser.expressions.length > 0) {
statement = call_child_payload(b.block([optimiser.apply(), statement]), true); statement = call_child_renderer(b.block([optimiser.apply(), statement]), true);
} }
if (dynamic && custom_css_props.length === 0) { if (dynamic && custom_css_props.length === 0) {

@ -207,7 +207,7 @@ export function build_element_attributes(node, context) {
context.state.template.push( context.state.template.push(
b.call( b.call(
'$.maybe_selected', '$.maybe_selected',
b.id('$$payload'), b.id(' $$renderer'),
b.member( b.member(
build_spread_object( build_spread_object(
node, node,
@ -264,7 +264,7 @@ export function build_element_attributes(node, context) {
context.state.template.push( context.state.template.push(
b.call( b.call(
'$.maybe_selected', '$.maybe_selected',
b.id('$$payload'), b.id(' $$renderer'),
literal_value != null ? b.literal(/** @type {any} */ (literal_value)) : b.void0 literal_value != null ? b.literal(/** @type {any} */ (literal_value)) : b.void0
) )
); );
@ -296,7 +296,7 @@ export function build_element_attributes(node, context) {
} }
if (name === 'value' && node.name === 'option') { if (name === 'value' && node.name === 'option') {
context.state.template.push(b.call('$.maybe_selected', b.id('$$payload'), value)); context.state.template.push(b.call('$.maybe_selected', b.id(' $$renderer'), value));
} }
} }
} }

@ -80,7 +80,7 @@ export function process_children(nodes, { visit, state }) {
flush(); flush();
const visited = /** @type {Expression} */ (visit(node.expression)); const visited = /** @type {Expression} */ (visit(node.expression));
state.template.push( state.template.push(
b.stmt(b.call('$$payload.push', b.thunk(b.call('$.escape', visited), true))) b.stmt(b.call(' $$renderer.push', b.thunk(b.call('$.escape', visited), true)))
); );
} else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') { } else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') {
sequence.push(node); sequence.push(node);
@ -119,7 +119,7 @@ export function build_template(template) {
statements.push( statements.push(
b.stmt( b.stmt(
b.call( b.call(
b.id('$$payload.push'), b.id(' $$renderer.push'),
b.template( b.template(
strings.map((cooked, i) => b.quasi(cooked, i === strings.length - 1)), strings.map((cooked, i) => b.quasi(cooked, i === strings.length - 1)),
expressions expressions
@ -266,8 +266,8 @@ export function build_getter(node, state) {
* @param {boolean} async * @param {boolean} async
* @returns {Statement} * @returns {Statement}
*/ */
export function call_child_payload(body, async) { export function call_child_renderer(body, async) {
return b.stmt(b.call('$$payload.child', b.arrow([b.id('$$payload')], body, async))); return b.stmt(b.call(' $$renderer.child', b.arrow([b.id(' $$renderer')], body, async)));
} }
/** /**
@ -275,9 +275,9 @@ export function call_child_payload(body, async) {
* @param {Identifier | false} component_fn_id * @param {Identifier | false} component_fn_id
* @returns {Statement} * @returns {Statement}
*/ */
export function call_component_payload(body, component_fn_id) { export function call_component_renderer(body, component_fn_id) {
return b.stmt( return b.stmt(
b.call('$$payload.component', b.arrow([b.id('$$payload')], body, false), component_fn_id) b.call(' $$renderer.component', b.arrow([b.id(' $$renderer')], body, false), component_fn_id)
); );
} }

@ -1,12 +1,12 @@
/** @import { SSRContext } from '#server' */ /** @import { SSRContext } from '#server' */
/** @import { Payload } from './internal/server/payload.js' */ /** @import { Renderer } from './internal/server/renderer.js' */
import { ssr_context } from './internal/server/context.js'; import { ssr_context } from './internal/server/context.js';
import { noop } from './internal/shared/utils.js'; import { noop } from './internal/shared/utils.js';
import * as e from './internal/server/errors.js'; import * as e from './internal/server/errors.js';
/** @param {() => void} fn */ /** @param {() => void} fn */
export function onDestroy(fn) { export function onDestroy(fn) {
/** @type {Payload} */ (/** @type {SSRContext} */ (ssr_context).r).on_destroy(fn); /** @type {Renderer} */ (/** @type {SSRContext} */ (ssr_context).r).on_destroy(fn);
} }
export { export {

@ -1,5 +1,5 @@
/** @import { Snippet } from 'svelte' */ /** @import { Snippet } from 'svelte' */
/** @import { Payload } from '../payload' */ /** @import { Renderer } from '../renderer' */
/** @import { Getters } from '#shared' */ /** @import { Getters } from '#shared' */
/** /**
@ -13,9 +13,9 @@
*/ */
export function createRawSnippet(fn) { export function createRawSnippet(fn) {
// @ts-expect-error the types are a lie // @ts-expect-error the types are a lie
return (/** @type {Payload} */ payload, /** @type {Params} */ ...args) => { return (/** @type {Renderer} */ renderer, /** @type {Params} */ ...args) => {
var getters = /** @type {Getters<Params>} */ (args.map((value) => () => value)); var getters = /** @type {Getters<Params>} */ (args.map((value) => () => value));
payload.push( renderer.push(
fn(...getters) fn(...getters)
.render() .render()
.trim() .trim()

@ -6,7 +6,7 @@ import {
} from '../../html-tree-validation.js'; } from '../../html-tree-validation.js';
import { set_ssr_context, ssr_context } from './context.js'; import { set_ssr_context, ssr_context } from './context.js';
import * as e from './errors.js'; import * as e from './errors.js';
import { Payload } from './payload.js'; import { Renderer } from './renderer.js';
// TODO move this // TODO move this
/** /**
@ -26,10 +26,10 @@ import { Payload } from './payload.js';
export let seen; export let seen;
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {string} message * @param {string} message
*/ */
function print_error(payload, message) { function print_error(renderer, message) {
message = message =
`node_invalid_placement_ssr: ${message}\n\n` + `node_invalid_placement_ssr: ${message}\n\n` +
'This can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.'; 'This can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.';
@ -39,19 +39,19 @@ function print_error(payload, message) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(message); console.error(message);
payload.child( renderer.child(
(payload) => payload.push(`<script>console.error(${JSON.stringify(message)})</script>`), (renderer) => renderer.push(`<script>console.error(${JSON.stringify(message)})</script>`),
'head' 'head'
); );
} }
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {string} tag * @param {string} tag
* @param {number} line * @param {number} line
* @param {number} column * @param {number} column
*/ */
export function push_element(payload, tag, line, column) { export function push_element(renderer, tag, line, column) {
var context = /** @type {SSRContext} */ (ssr_context); var context = /** @type {SSRContext} */ (ssr_context);
var filename = context.function[FILENAME]; var filename = context.function[FILENAME];
var parent = context.element; var parent = context.element;
@ -67,7 +67,7 @@ export function push_element(payload, tag, line, column) {
: undefined; : undefined;
const message = is_tag_valid_with_parent(tag, parent.tag, child_loc, parent_loc); const message = is_tag_valid_with_parent(tag, parent.tag, child_loc, parent_loc);
if (message) print_error(payload, message); if (message) print_error(renderer, message);
while (ancestor != null) { while (ancestor != null) {
ancestors.push(ancestor.tag); ancestors.push(ancestor.tag);
@ -76,7 +76,7 @@ export function push_element(payload, tag, line, column) {
: undefined; : undefined;
const message = is_tag_valid_with_ancestor(tag, ancestors, child_loc, ancestor_loc); const message = is_tag_valid_with_ancestor(tag, ancestors, child_loc, ancestor_loc);
if (message) print_error(payload, message); if (message) print_error(renderer, message);
ancestor = ancestor.parent; ancestor = ancestor.parent;
} }
@ -90,13 +90,13 @@ export function pop_element() {
} }
/** /**
* @param {Payload} payload * @param {Renderer} renderer
*/ */
export function validate_snippet_args(payload) { export function validate_snippet_args(renderer) {
if ( if (
typeof payload !== 'object' || typeof renderer !== 'object' ||
// for some reason typescript consider the type of payload as never after the first instanceof // for some reason typescript consider the type of renderer as never after the first instanceof
!(payload instanceof Payload) !(renderer instanceof Renderer)
) { ) {
e.invalid_snippet_arguments(); e.invalid_snippet_arguments();
} }

@ -1,7 +1,7 @@
/** @import { ComponentType, SvelteComponent, Component } from 'svelte' */ /** @import { ComponentType, SvelteComponent, Component } from 'svelte' */
/** @import { RenderOutput } from '#server' */ /** @import { RenderOutput } from '#server' */
/** @import { Store } from '#shared' */ /** @import { Store } from '#shared' */
/** @import { AccumulatedContent } from './payload.js' */ /** @import { AccumulatedContent } from './renderer.js' */
export { FILENAME, HMR } from '../../constants.js'; export { FILENAME, HMR } from '../../constants.js';
import { attr, clsx, to_class, to_style } from '../shared/attributes.js'; import { attr, clsx, to_class, to_style } from '../shared/attributes.js';
import { is_promise, noop } from '../shared/utils.js'; import { is_promise, noop } from '../shared/utils.js';
@ -17,7 +17,7 @@ import { DEV } from 'esm-env';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
import { validate_store } from '../shared/validate.js'; import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
import { Payload } from './payload.js'; import { Renderer } from './renderer.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter // https://infra.spec.whatwg.org/#noncharacter
@ -25,30 +25,30 @@ const INVALID_ATTR_NAME_CHAR_REGEX =
/[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {string} tag * @param {string} tag
* @param {() => void} attributes_fn * @param {() => void} attributes_fn
* @param {() => void} children_fn * @param {() => void} children_fn
* @returns {void} * @returns {void}
*/ */
export function element(payload, tag, attributes_fn = noop, children_fn = noop) { export function element(renderer, tag, attributes_fn = noop, children_fn = noop) {
payload.push('<!---->'); renderer.push('<!---->');
if (tag) { if (tag) {
payload.push(`<${tag}`); renderer.push(`<${tag}`);
attributes_fn(); attributes_fn();
payload.push(`>`); renderer.push(`>`);
if (!is_void(tag)) { if (!is_void(tag)) {
children_fn(); children_fn();
if (!is_raw_text_element(tag)) { if (!is_raw_text_element(tag)) {
payload.push(EMPTY_COMMENT); renderer.push(EMPTY_COMMENT);
} }
payload.push(`</${tag}>`); renderer.push(`</${tag}>`);
} }
} }
payload.push('<!---->'); renderer.push('<!---->');
} }
/** /**
@ -60,49 +60,49 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
* @returns {RenderOutput} * @returns {RenderOutput}
*/ */
export function render(component, options = {}) { export function render(component, options = {}) {
return Payload.render(/** @type {Component<Props>} */ (component), options); return Renderer.render(/** @type {Component<Props>} */ (component), options);
} }
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {(payload: Payload) => Promise<void> | void} fn * @param {(renderer: Renderer) => Promise<void> | void} fn
* @returns {void} * @returns {void}
*/ */
export function head(payload, fn) { export function head(renderer, fn) {
payload.child((payload) => { renderer.child((renderer) => {
payload.push(BLOCK_OPEN); renderer.push(BLOCK_OPEN);
payload.child(fn); renderer.child(fn);
payload.push(BLOCK_CLOSE); renderer.push(BLOCK_CLOSE);
}, 'head'); }, 'head');
} }
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {boolean} is_html * @param {boolean} is_html
* @param {Record<string, string>} props * @param {Record<string, string>} props
* @param {() => void} component * @param {() => void} component
* @param {boolean} dynamic * @param {boolean} dynamic
* @returns {void} * @returns {void}
*/ */
export function css_props(payload, is_html, props, component, dynamic = false) { export function css_props(renderer, is_html, props, component, dynamic = false) {
const styles = style_object_to_string(props); const styles = style_object_to_string(props);
if (is_html) { if (is_html) {
payload.push(`<svelte-css-wrapper style="display: contents; ${styles}">`); renderer.push(`<svelte-css-wrapper style="display: contents; ${styles}">`);
} else { } else {
payload.push(`<g style="${styles}">`); renderer.push(`<g style="${styles}">`);
} }
if (dynamic) { if (dynamic) {
payload.push('<!---->'); renderer.push('<!---->');
} }
component(); component();
if (is_html) { if (is_html) {
payload.push(`<!----></svelte-css-wrapper>`); renderer.push(`<!----></svelte-css-wrapper>`);
} else { } else {
payload.push(`<!----></g>`); renderer.push(`<!----></g>`);
} }
} }
@ -304,14 +304,14 @@ export function unsubscribe_stores(store_values) {
} }
/** /**
* @param {Payload} payload * @param {Renderer} renderer
* @param {Record<string, any>} $$props * @param {Record<string, any>} $$props
* @param {string} name * @param {string} name
* @param {Record<string, unknown>} slot_props * @param {Record<string, unknown>} slot_props
* @param {null | (() => void)} fallback_fn * @param {null | (() => void)} fallback_fn
* @returns {void} * @returns {void}
*/ */
export function slot(payload, $$props, name, slot_props, fallback_fn) { export function slot(renderer, $$props, name, slot_props, fallback_fn) {
var slot_fn = $$props.$$slots?.[name]; var slot_fn = $$props.$$slots?.[name];
// Interop: Can use snippets to fill slots // Interop: Can use snippets to fill slots
if (slot_fn === true) { if (slot_fn === true) {
@ -319,7 +319,7 @@ export function slot(payload, $$props, name, slot_props, fallback_fn) {
} }
if (slot_fn !== undefined) { if (slot_fn !== undefined) {
slot_fn(payload, slot_props); slot_fn(renderer, slot_props);
} else { } else {
fallback_fn?.(); fallback_fn?.();
} }
@ -387,21 +387,21 @@ export function bind_props(props_parent, props_now) {
/** /**
* @template V * @template V
* @param {Payload} payload * @param {Renderer} renderer
* @param {Promise<V>} promise * @param {Promise<V>} promise
* @param {null | (() => void)} pending_fn * @param {null | (() => void)} pending_fn
* @param {(value: V) => void} then_fn * @param {(value: V) => void} then_fn
* @returns {void} * @returns {void}
*/ */
function await_block(payload, promise, pending_fn, then_fn) { function await_block(renderer, promise, pending_fn, then_fn) {
if (is_promise(promise)) { if (is_promise(promise)) {
payload.push(BLOCK_OPEN); renderer.push(BLOCK_OPEN);
promise.then(null, noop); promise.then(null, noop);
if (pending_fn !== null) { if (pending_fn !== null) {
pending_fn(); pending_fn();
} }
} else if (then_fn !== null) { } else if (then_fn !== null) {
payload.push(BLOCK_OPEN_ELSE); renderer.push(BLOCK_OPEN_ELSE);
then_fn(promise); then_fn(promise);
} }
} }
@ -443,12 +443,12 @@ export function once(get_value) {
/** /**
* Create an unique ID * Create an unique ID
* @param {Payload} payload * @param {Renderer} renderer
* @returns {string} * @returns {string}
*/ */
export function props_id(payload) { export function props_id(renderer) {
const uid = payload.global.uid(); const uid = renderer.global.uid();
payload.push('<!--#' + uid + '-->'); renderer.push('<!--#' + uid + '-->');
return uid; return uid;
} }
@ -496,11 +496,11 @@ export function derived(fn) {
/** /**
* *
* @param {Payload} payload * @param {Renderer} renderer
* @param {unknown} value * @param {unknown} value
*/ */
export function maybe_selected(payload, value) { export function maybe_selected(renderer, value) {
return value === payload.local.select_value ? ' selected' : ''; return value === renderer.local.select_value ? ' selected' : '';
} }
/** /**
@ -508,25 +508,25 @@ export function maybe_selected(payload, value) {
* content as its `value` to determine whether we should apply the `selected` attribute. * content as its `value` to determine whether we should apply the `selected` attribute.
* This has to be done at runtime, for hopefully obvious reasons. It is also complicated, * This has to be done at runtime, for hopefully obvious reasons. It is also complicated,
* for sad reasons. * for sad reasons.
* @param {Payload} payload * @param {Renderer} renderer
* @param {((payload: Payload) => void | Promise<void>)} children * @param {((renderer: Renderer) => void | Promise<void>)} children
* @returns {void} * @returns {void}
*/ */
export function valueless_option(payload, children) { export function valueless_option(renderer, children) {
const i = payload.length; const i = renderer.length;
// prior to children, `payload` has some combination of string/unresolved payload that ends in `<option ...>` // prior to children, `renderer` has some combination of string/unresolved renderer that ends in `<option ...>`
payload.child(children); renderer.child(children);
// post-children, `payload` has child content, possibly also with some number of hydration comments. // post-children, `renderer` has child content, possibly also with some number of hydration comments.
// we can compact this last chunk of content to see if it matches the select value... // we can compact this last chunk of content to see if it matches the select value...
payload.compact({ renderer.compact({
start: i, start: i,
fn: (content) => { fn: (content) => {
if (content.body.replace(/<!---->/g, '') === payload.local.select_value) { if (content.body.replace(/<!---->/g, '') === renderer.local.select_value) {
// ...and if it does match the select value, we can compact the part of the payload representing the `<option ...>` // ...and if it does match the select value, we can compact the part of the renderer representing the `<option ...>`
// to add the `selected` attribute to the end. // to add the `selected` attribute to the end.
payload.compact({ renderer.compact({
start: i - 1, start: i - 1,
end: i, end: i,
fn: (content) => { fn: (content) => {
@ -545,10 +545,10 @@ export function valueless_option(payload, children) {
* by running the children and passing the resulting value, which means * by running the children and passing the resulting value, which means
* we don't have to do all of the same parsing nonsense. It also means we can avoid * we don't have to do all of the same parsing nonsense. It also means we can avoid
* coercing everything to a string. * coercing everything to a string.
* @param {Payload} payload * @param {Renderer} renderer
* @param {(() => unknown)} child * @param {(() => unknown)} child
*/ */
export function simple_valueless_option(payload, child) { export function simple_valueless_option(renderer, child) {
const result = child(); const result = child();
/** /**
@ -557,14 +557,14 @@ export function simple_valueless_option(payload, child) {
* @returns {AccumulatedContent} * @returns {AccumulatedContent}
*/ */
const mark_selected = (content, child_value) => { const mark_selected = (content, child_value) => {
if (child_value === payload.local.select_value) { if (child_value === renderer.local.select_value) {
return { body: content.body.slice(0, -1) + ' selected>', head: content.head }; return { body: content.body.slice(0, -1) + ' selected>', head: content.head };
} }
return content; return content;
}; };
payload.compact({ renderer.compact({
start: payload.length - 1, start: renderer.length - 1,
fn: (content) => { fn: (content) => {
if (result instanceof Promise) { if (result instanceof Promise) {
return result.then((child_value) => mark_selected(content, child_value)); return result.then((child_value) => mark_selected(content, child_value));
@ -573,13 +573,13 @@ export function simple_valueless_option(payload, child) {
} }
}); });
payload.child((child_payload) => { renderer.child((child_renderer) => {
if (result instanceof Promise) { if (result instanceof Promise) {
return result.then((child_value) => { return result.then((child_value) => {
child_payload.push(escape_html(child_value)); child_renderer.push(escape_html(child_value));
}); });
} }
child_payload.push(escape_html(result)); child_renderer.push(escape_html(result));
}); });
} }
@ -588,23 +588,23 @@ export function simple_valueless_option(payload, child) {
* which one "wins". To do this, we perform a depth-first comparison of where the title was encountered -- * which one "wins". To do this, we perform a depth-first comparison of where the title was encountered --
* later ones "win" over earlier ones, regardless of what order the promises resolve in. To accomodate this, we: * later ones "win" over earlier ones, regardless of what order the promises resolve in. To accomodate this, we:
* - Figure out where we are in the content tree (`get_path`) * - Figure out where we are in the content tree (`get_path`)
* - Render the title in its own child so that it has a defined "slot" in the payload * - Render the title in its own child so that it has a defined "slot" in the renderer
* - Compact that spot so that we get the entire rendered contents of the title * - Compact that spot so that we get the entire rendered contents of the title
* - Attempt to set the global title (this is where the "wins" logic based on the path happens) * - Attempt to set the global title (this is where the "wins" logic based on the path happens)
* *
* TODO we could optimize this by not even rendering the title if the path wouldn't be accepted * TODO we could optimize this by not even rendering the title if the path wouldn't be accepted
* *
* @param {Payload} payload * @param {Renderer} renderer
* @param {((payload: Payload) => void | Promise<void>)} children * @param {((renderer: Renderer) => void | Promise<void>)} children
*/ */
export function build_title(payload, children) { export function build_title(renderer, children) {
const path = payload.get_path(); const path = renderer.get_path();
const i = payload.length; const i = renderer.length;
payload.child(children); renderer.child(children);
payload.compact({ renderer.compact({
start: i, start: i,
fn: ({ head }) => { fn: ({ head }) => {
payload.global.set_title(head, path); renderer.global.set_title(head, path);
// since we can only ever render the title in this chunk, and title rendering is handled specially, // since we can only ever render the title in this chunk, and title rendering is handled specially,
// we can just ditch the results after we've saved them globally // we can just ditch the results after we've saved them globally
return { head: '', body: '' }; return { head: '', body: '' };

@ -7,57 +7,57 @@ import * as e from './errors.js';
import * as w from './warnings.js'; import * as w from './warnings.js';
import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
/** @typedef {'head' | 'body'} PayloadType */ /** @typedef {'head' | 'body'} RendererType */
/** @typedef {{ [key in PayloadType]: string }} AccumulatedContent */ /** @typedef {{ [key in RendererType]: string }} AccumulatedContent */
/** /**
* @template T * @template T
* @typedef {T | Promise<T>} MaybePromise<T> * @typedef {T | Promise<T>} MaybePromise<T>
*/ */
/** /**
* @typedef {string | Payload} PayloadItem * @typedef {string | Renderer} RendererItem
*/ */
/** /**
* Payloads are basically a tree of `string | Payload`s, where each `Payload` in the tree represents * Renderers are basically a tree of `string | Renderer`s, where each `Renderer` in the tree represents
* work that may or may not have completed. A payload can be {@link collect}ed to aggregate the * work that may or may not have completed. A renderer can be {@link collect}ed to aggregate the
* content from itself and all of its children, but this will throw if any of the children are * content from itself and all of its children, but this will throw if any of the children are
* performing asynchronous work. To asynchronously collect a payload, just `await` it. * performing asynchronous work. To asynchronously collect a renderer, just `await` it.
* *
* The `string` values within a payload are always associated with the {@link type} of that payload. To switch types, * The `string` values within a renderer are always associated with the {@link type} of that renderer. To switch types,
* call {@link child} with a different `type` argument. * call {@link child} with a different `type` argument.
*/ */
export class Payload { export class Renderer {
/** /**
* The contents of the payload. * The contents of the renderer.
* @type {PayloadItem[]} * @type {RendererItem[]}
*/ */
#out = []; #out = [];
/** /**
* Any `onDestroy` callbacks registered during execution of this payload. * Any `onDestroy` callbacks registered during execution of this renderer.
* @type {(() => void)[] | undefined} * @type {(() => void)[] | undefined}
*/ */
#on_destroy = undefined; #on_destroy = undefined;
/** /**
* Whether this payload is a component body. * Whether this renderer is a component body.
* @type {boolean} * @type {boolean}
*/ */
#is_component_body = false; #is_component_body = false;
/** /**
* The type of string content that this payload is accumulating. * The type of string content that this renderer is accumulating.
* @type {PayloadType} * @type {RendererType}
*/ */
type; type;
/** @type {Payload | undefined} */ /** @type {Renderer | undefined} */
#parent; #parent;
/** /**
* Asynchronous work associated with this payload. `initial` is the promise from the function * Asynchronous work associated with this renderer. `initial` is the promise from the function
* this payload was passed to (if that function was async), and `followup` is any any additional * this renderer was passed to (if that function was async), and `followup` is any any additional
* work from `compact` calls that needs to complete prior to collecting this payload's content. * work from `compact` calls that needs to complete prior to collecting this renderer's content.
* @type {{ initial: Promise<void> | undefined, followup: Promise<void>[] }} * @type {{ initial: Promise<void> | undefined, followup: Promise<void>[] }}
*/ */
promises = { initial: undefined, followup: [] }; promises = { initial: undefined, followup: [] };
@ -80,8 +80,8 @@ export class Payload {
/** /**
* @param {SSRState} global * @param {SSRState} global
* @param {Payload | undefined} [parent] * @param {Renderer | undefined} [parent]
* @param {PayloadType} [type] * @param {RendererType} [type]
*/ */
constructor(global, parent, type) { constructor(global, parent, type) {
this.global = global; this.global = global;
@ -91,13 +91,13 @@ export class Payload {
} }
/** /**
* Create a child payload. The child payload 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 {(payload: Payload) => MaybePromise<void>} fn * @param {(renderer: Renderer) => MaybePromise<void>} fn
* @param {PayloadType} [type] * @param {RendererType} [type]
*/ */
child(fn, type) { child(fn, type) {
const child = new Payload(this.global, this, type); const child = new Renderer(this.global, this, type);
this.#out.push(child); this.#out.push(child);
const parent = ssr_context; const parent = ssr_context;
@ -126,9 +126,9 @@ export class Payload {
} }
/** /**
* Create a component payload. The component payload inherits the state from the parent, * Create a component renderer. The component renderer inherits the state from the parent,
* but has its own content. It is treated as an ordering boundary for ondestroy callbacks. * but has its own content. It is treated as an ordering boundary for ondestroy callbacks.
* @param {(payload: Payload) => MaybePromise<void>} fn * @param {(renderer: Renderer) => MaybePromise<void>} fn
* @param {Function} [component_fn] * @param {Function} [component_fn]
* @returns {void} * @returns {void}
*/ */
@ -144,25 +144,25 @@ export class Payload {
*/ */
push(content) { push(content) {
if (typeof content === 'function') { if (typeof content === 'function') {
this.child(async (payload) => payload.push(await content())); this.child(async (renderer) => renderer.push(await content()));
} else { } else {
this.#out.push(content); this.#out.push(content);
} }
} }
/** /**
* Compact everything between `start` and `end` into a single payload, then call `fn` with the result of that payload. * Compact everything between `start` and `end` into a single renderer, then call `fn` with the result of that renderer.
* The compacted payload will be sync if all of the children are sync and {@link fn} is sync, otherwise it will be async. * The compacted renderer will be sync if all of the children are sync and {@link fn} is sync, otherwise it will be async.
* @param {{ start: number, end?: number, fn: (content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent> }} args * @param {{ start: number, end?: number, fn: (content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent> }} args
*/ */
compact({ start, end = this.#out.length, fn }) { compact({ start, end = this.#out.length, fn }) {
const child = new Payload(this.global, this); const child = new Renderer(this.global, this);
const to_compact = this.#out.splice(start, end - start, child); const to_compact = this.#out.splice(start, end - start, child);
if (this.global.mode === 'sync') { if (this.global.mode === 'sync') {
Payload.#compact(fn, child, to_compact, this.type); Renderer.#compact(fn, child, to_compact, this.type);
} else { } else {
this.promises.followup.push(Payload.#compact_async(fn, child, to_compact, this.type)); this.promises.followup.push(Renderer.#compact_async(fn, child, to_compact, this.type));
} }
} }
@ -184,26 +184,26 @@ export class Payload {
* @deprecated this is needed for legacy component bindings * @deprecated this is needed for legacy component bindings
*/ */
copy() { copy() {
const copy = new Payload(this.global, this.#parent, this.type); const copy = new Renderer(this.global, this.#parent, this.type);
copy.#out = this.#out.map((item) => (item instanceof Payload ? 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;
} }
/** /**
* @param {Payload} other * @param {Renderer} other
* @deprecated this is needed for legacy component bindings * @deprecated this is needed for legacy component bindings
*/ */
subsume(other) { subsume(other) {
if (this.global.mode !== other.global.mode) { if (this.global.mode !== other.global.mode) {
throw new Error( throw new Error(
"invariant: A payload cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!" "invariant: A renderer cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!"
); );
} }
this.local = other.local; this.local = other.local;
this.#out = other.#out.map((item) => { this.#out = other.#out.map((item) => {
if (item instanceof Payload) { if (item instanceof Renderer) {
item.subsume(item); item.subsume(item);
} }
return item; return item;
@ -236,17 +236,17 @@ export class Payload {
Object.defineProperties(result, { Object.defineProperties(result, {
html: { html: {
get: () => { get: () => {
return (sync ??= Payload.#render(component, options)).body; return (sync ??= Renderer.#render(component, options)).body;
} }
}, },
head: { head: {
get: () => { get: () => {
return (sync ??= Payload.#render(component, options)).head; return (sync ??= Renderer.#render(component, options)).head;
} }
}, },
body: { body: {
get: () => { get: () => {
return (sync ??= Payload.#render(component, options)).body; return (sync ??= Renderer.#render(component, options)).body;
} }
}, },
then: { then: {
@ -262,7 +262,7 @@ export class Payload {
(onfulfilled, onrejected) => { (onfulfilled, onrejected) => {
if (!async_mode_flag) { if (!async_mode_flag) {
w.experimental_async_ssr(); w.experimental_async_ssr();
const result = (sync ??= Payload.#render(component, options)); const result = (sync ??= Renderer.#render(component, options));
const user_result = onfulfilled({ const user_result = onfulfilled({
head: result.head, head: result.head,
body: result.body, body: result.body,
@ -270,7 +270,7 @@ export class Payload {
}); });
return Promise.resolve(user_result); return Promise.resolve(user_result);
} }
async ??= Payload.#render_async(component, options); async ??= Renderer.#render_async(component, options);
return async.then((result) => { return async.then((result) => {
Object.defineProperty(result, 'html', { Object.defineProperty(result, 'html', {
// eslint-disable-next-line getter-return // eslint-disable-next-line getter-return
@ -291,8 +291,8 @@ export class Payload {
* Collect all of the `onDestroy` callbacks regsitered during rendering. In an async context, this is only safe to call * Collect all of the `onDestroy` callbacks regsitered during rendering. In an async context, this is only safe to call
* after awaiting `collect_async`. * after awaiting `collect_async`.
* *
* Child payloads are "porous" and don't affect execution order, but component body payloads * Child renderers are "porous" and don't affect execution order, but component body renderers
* create ordering boundaries. Within a payload, callbacks run in order until hitting a component boundary. * create ordering boundaries. Within a renderer, callbacks run in order until hitting a component boundary.
* @returns {Iterable<() => void>} * @returns {Iterable<() => void>}
*/ */
*#collect_on_destroy() { *#collect_on_destroy() {
@ -302,8 +302,8 @@ export class Payload {
} }
/** /**
* Performs a depth-first search of payloads, yielding the deepest components first, then additional components as we backtrack up the tree. * Performs a depth-first search of renderers, yielding the deepest components first, then additional components as we backtrack up the tree.
* @returns {Iterable<Payload>} * @returns {Iterable<Renderer>}
*/ */
*#traverse_components() { *#traverse_components() {
for (const child of this.#out) { for (const child of this.#out) {
@ -326,7 +326,7 @@ export class Payload {
} }
} }
for (const child of this.#out) { for (const child of this.#out) {
if (child instanceof Payload && !child.#is_component_body) { if (child instanceof Renderer && !child.#is_component_body) {
yield* child.#collect_ondestroy(); yield* child.#collect_ondestroy();
} }
} }
@ -343,10 +343,10 @@ export class Payload {
static #render(component, options) { static #render(component, options) {
var previous_context = ssr_context; var previous_context = ssr_context;
try { try {
const payload = Payload.#open_render('sync', component, options); const renderer = Renderer.#open_render('sync', component, options);
const content = Payload.#collect_content([payload], payload.type); const content = Renderer.#collect_content([renderer], renderer.type);
return Payload.#close_render(content, payload); return Renderer.#close_render(content, renderer);
} finally { } finally {
abort(); abort();
set_ssr_context(previous_context); set_ssr_context(previous_context);
@ -364,10 +364,10 @@ export class Payload {
static async #render_async(component, options) { static async #render_async(component, options) {
var previous_context = ssr_context; var previous_context = ssr_context;
try { try {
const payload = Payload.#open_render('async', component, options); const renderer = Renderer.#open_render('async', component, options);
const content = await Payload.#collect_content_async([payload], payload.type); const content = await Renderer.#collect_content_async([renderer], renderer.type);
return Payload.#close_render(content, payload); return Renderer.#close_render(content, renderer);
} finally { } finally {
abort(); abort();
set_ssr_context(previous_context); set_ssr_context(previous_context);
@ -376,38 +376,38 @@ export class Payload {
/** /**
* @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn * @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn
* @param {Payload} child * @param {Renderer} child
* @param {PayloadItem[]} to_compact * @param {RendererItem[]} to_compact
* @param {PayloadType} type * @param {RendererType} type
*/ */
static #compact(fn, child, to_compact, type) { static #compact(fn, child, to_compact, type) {
const content = Payload.#collect_content(to_compact, type); const content = Renderer.#collect_content(to_compact, type);
const transformed_content = fn(content); const transformed_content = fn(content);
if (transformed_content instanceof Promise) { if (transformed_content instanceof Promise) {
throw new Error( throw new Error(
"invariant: Somehow you've encountered asynchronous work while rendering synchronously. If you're seeing this, there's a compiler bug. File an issue!" "invariant: Somehow you've encountered asynchronous work while rendering synchronously. If you're seeing this, there's a compiler bug. File an issue!"
); );
} else { } else {
Payload.#push_accumulated_content(child, transformed_content); Renderer.#push_accumulated_content(child, transformed_content);
} }
} }
/** /**
* @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn * @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn
* @param {Payload} child * @param {Renderer} child
* @param {PayloadItem[]} to_compact * @param {RendererItem[]} to_compact
* @param {PayloadType} type * @param {RendererType} type
*/ */
static async #compact_async(fn, child, to_compact, type) { static async #compact_async(fn, child, to_compact, type) {
const content = await Payload.#collect_content_async(to_compact, type); const content = await Renderer.#collect_content_async(to_compact, type);
const transformed_content = await fn(content); const transformed_content = await fn(content);
Payload.#push_accumulated_content(child, transformed_content); Renderer.#push_accumulated_content(child, transformed_content);
} }
/** /**
* Collect all of the code from the `out` array and return it as a string, or a promise resolving to a string. * Collect all of the code from the `out` array and return it as a string, or a promise resolving to a string.
* @param {PayloadItem[]} items * @param {RendererItem[]} items
* @param {PayloadType} current_type * @param {RendererType} current_type
* @param {AccumulatedContent} content * @param {AccumulatedContent} content
* @returns {AccumulatedContent} * @returns {AccumulatedContent}
*/ */
@ -415,8 +415,8 @@ export class Payload {
for (const item of items) { for (const item of items) {
if (typeof item === 'string') { if (typeof item === 'string') {
content[current_type] += item; content[current_type] += item;
} else if (item instanceof Payload) { } else if (item instanceof Renderer) {
Payload.#collect_content(item.#out, item.type, content); Renderer.#collect_content(item.#out, item.type, content);
} }
} }
return content; return content;
@ -424,8 +424,8 @@ export class Payload {
/** /**
* Collect all of the code from the `out` array and return it as a string. * Collect all of the code from the `out` array and return it as a string.
* @param {PayloadItem[]} items * @param {RendererItem[]} items
* @param {PayloadType} current_type * @param {RendererType} current_type
* @param {AccumulatedContent} content * @param {AccumulatedContent} content
* @returns {Promise<AccumulatedContent>} * @returns {Promise<AccumulatedContent>}
*/ */
@ -436,7 +436,7 @@ export class Payload {
content[current_type] += item; content[current_type] += item;
} else { } else {
if (item.promises.initial) { if (item.promises.initial) {
// this represents the async function that's modifying this payload. // this represents the async function that's modifying this renderer.
// we can't do anything until it's done and we know our `out` array is complete. // we can't do anything until it's done and we know our `out` array is complete.
await item.promises.initial; await item.promises.initial;
} }
@ -444,20 +444,20 @@ export class Payload {
// this is sequential because `compact` could synchronously queue up additional followup work // this is sequential because `compact` could synchronously queue up additional followup work
await followup; await followup;
} }
await Payload.#collect_content_async(item.#out, item.type, content); await Renderer.#collect_content_async(item.#out, item.type, content);
} }
} }
return content; return content;
} }
/** /**
* @param {Payload} tree * @param {Renderer} tree
* @param {AccumulatedContent} accumulated_content * @param {AccumulatedContent} accumulated_content
*/ */
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 Payload(tree.global, tree, /** @type {PayloadType} */ (type)); const child = new Renderer(tree.global, tree, /** @type {RendererType} */ (type));
child.push(content); child.push(content);
tree.#out.push(child); tree.#out.push(child);
} }
@ -468,45 +468,47 @@ export class Payload {
* @param {'sync' | 'async'} mode * @param {'sync' | 'async'} mode
* @param {import('svelte').Component<Props>} component * @param {import('svelte').Component<Props>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options
* @returns {Payload} * @returns {Renderer}
*/ */
static #open_render(mode, component, options) { static #open_render(mode, component, options) {
const payload = new Payload(new SSRState(mode, options.idPrefix ? options.idPrefix + '-' : '')); const renderer = new Renderer(
new SSRState(mode, options.idPrefix ? options.idPrefix + '-' : '')
);
payload.push(BLOCK_OPEN); renderer.push(BLOCK_OPEN);
if (options.context) { if (options.context) {
push(); push();
/** @type {SSRContext} */ (ssr_context).c = options.context; /** @type {SSRContext} */ (ssr_context).c = options.context;
/** @type {SSRContext} */ (ssr_context).r = payload; /** @type {SSRContext} */ (ssr_context).r = renderer;
} }
// @ts-expect-error // @ts-expect-error
component(payload, options.props ?? {}); component(renderer, options.props ?? {});
if (options.context) { if (options.context) {
pop(); pop();
} }
payload.push(BLOCK_CLOSE); renderer.push(BLOCK_CLOSE);
return payload; return renderer;
} }
/** /**
* @param {AccumulatedContent} content * @param {AccumulatedContent} content
* @param {Payload} payload * @param {Renderer} renderer
*/ */
static #close_render(content, payload) { static #close_render(content, renderer) {
for (const cleanup of payload.#collect_on_destroy()) { for (const cleanup of renderer.#collect_on_destroy()) {
cleanup(); cleanup();
} }
let head = content.head + payload.global.get_title(); let head = content.head + renderer.global.get_title();
const body = BLOCK_OPEN + content.body + BLOCK_CLOSE; // this inserts a fake boundary so hydration matches const body = BLOCK_OPEN + content.body + BLOCK_CLOSE; // this inserts a fake boundary so hydration matches
for (const { hash, code } of payload.global.css) { for (const { hash, code } of renderer.global.css) {
head += `<style id="${hash}">${code}</style>`; head += `<style id="${hash}">${code}</style>`;
} }

@ -1,68 +1,68 @@
import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import { afterAll, beforeAll, describe, expect, test } from 'vitest';
import { Payload, SSRState } from './payload.js'; import { Renderer, SSRState } from './renderer.js';
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js'; import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js';
test('collects synchronous body content by default', () => { test('collects synchronous body content by default', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.child(($$payload) => { renderer.child(($$renderer) => {
$$payload.push('b'); $$renderer.push('b');
}); });
payload.push('c'); renderer.push('c');
}; };
const { head, body } = Payload.render(component as unknown as Component); const { head, body } = Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->abc<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->abc<!--]--><!--]-->');
}); });
test('child type switches content area (head vs body)', () => { test('child type switches content area (head vs body)', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.child(($$payload) => { renderer.child(($$renderer) => {
$$payload.push('<title>T</title>'); $$renderer.push('<title>T</title>');
}, 'head'); }, 'head');
payload.push('b'); renderer.push('b');
}; };
const { head, body } = Payload.render(component as unknown as Component); const { head, body } = Renderer.render(component as unknown as Component);
expect(head).toBe('<title>T</title>'); expect(head).toBe('<title>T</title>');
expect(body).toBe('<!--[--><!--[-->ab<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->ab<!--]--><!--]-->');
}); });
test('child inherits parent type when not specified', () => { test('child inherits parent type when not specified', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.child((payload) => { renderer.child((renderer) => {
payload.push('<meta name="x"/>'); renderer.push('<meta name="x"/>');
payload.child((payload) => { renderer.child((renderer) => {
payload.push('<style>/* css */</style>'); renderer.push('<style>/* css */</style>');
}); });
}, 'head'); }, 'head');
}; };
const { head, body } = Payload.render(component as unknown as Component); const { head, body } = Renderer.render(component as unknown as Component);
expect(body).toBe('<!--[--><!--[--><!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[--><!--]--><!--]-->');
expect(head).toBe('<meta name="x"/><style>/* css */</style>'); expect(head).toBe('<meta name="x"/><style>/* css */</style>');
}); });
test('get_path returns the path indexes to a payload', () => { test('get_path returns the path indexes to a renderer', () => {
const root = new Payload(new SSRState('sync')); const root = new Renderer(new SSRState('sync'));
let child_a: InstanceType<typeof Payload> | undefined; let child_a: InstanceType<typeof Renderer> | undefined;
let child_b: InstanceType<typeof Payload> | undefined; let child_b: InstanceType<typeof Renderer> | undefined;
let child_b_0: InstanceType<typeof Payload> | undefined; let child_b_0: InstanceType<typeof Renderer> | undefined;
root.child(($$payload) => { root.child(($$renderer) => {
child_a = $$payload; child_a = $$renderer;
$$payload.push('A'); $$renderer.push('A');
}); });
root.child(($$payload) => { root.child(($$renderer) => {
child_b = $$payload; child_b = $$renderer;
$$payload.child(($$inner) => { $$renderer.child(($$inner) => {
child_b_0 = $$inner; child_b_0 = $$inner;
$$inner.push('B0'); $$inner.push('B0');
}); });
$$payload.push('B1'); $$renderer.push('B1');
}); });
expect(child_a!.get_path()).toEqual([0]); expect(child_a!.get_path()).toEqual([0]);
@ -71,26 +71,26 @@ test('get_path returns the path indexes to a payload', () => {
}); });
test('creating an async child in a sync context throws', () => { test('creating an async child in a sync context throws', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.child(async ($$payload) => { renderer.child(async ($$renderer) => {
await Promise.resolve(); await Promise.resolve();
$$payload.push('x'); $$renderer.push('x');
}); });
}; };
expect(() => Payload.render(component as unknown as Component).head).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).head).toThrow('await_invalid');
expect(() => Payload.render(component as unknown as Component).html).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).html).toThrow('await_invalid');
expect(() => Payload.render(component as unknown as Component).body).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).body).toThrow('await_invalid');
}); });
test('compact synchronously aggregates a range and can transform into head/body', () => { test('compact synchronously aggregates a range and can transform into head/body', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
const start = payload.length; const start = renderer.length;
payload.push('a'); renderer.push('a');
payload.push('b'); renderer.push('b');
payload.push('c'); renderer.push('c');
payload.compact({ renderer.compact({
start, start,
end: start + 2, end: start + 2,
fn: (content) => { fn: (content) => {
@ -99,17 +99,17 @@ test('compact synchronously aggregates a range and can transform into head/body'
}); });
}; };
const { head, body } = Payload.render(component as unknown as Component); const { head, body } = Renderer.render(component as unknown as Component);
expect(head).toBe('<h>H</h>'); expect(head).toBe('<h>H</h>');
expect(body).toBe('<!--[--><!--[-->abdc<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->abdc<!--]--><!--]-->');
}); });
test('local state is shallow-copied to children', () => { test('local state is shallow-copied to children', () => {
const root = new Payload(new SSRState('sync')); const root = new Renderer(new SSRState('sync'));
root.local.select_value = 'A'; root.local.select_value = 'A';
let child: InstanceType<typeof Payload> | undefined; let child: InstanceType<typeof Renderer> | undefined;
root.child(($$payload) => { root.child(($$renderer) => {
child = $$payload; child = $$renderer;
}); });
expect(child!.local.select_value).toBe('A'); expect(child!.local.select_value).toBe('A');
@ -118,14 +118,14 @@ 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 Payload(new SSRState('async'), undefined, 'head'); const a = new Renderer(new SSRState('async'), undefined, 'head');
a.push('<meta />'); a.push('<meta />');
a.local.select_value = 'A'; a.local.select_value = 'A';
const b = new Payload(new SSRState('async')); const b = new Renderer(new SSRState('async'));
b.child(async ($$payload) => { b.child(async ($$renderer) => {
await Promise.resolve(); await Promise.resolve();
$$payload.push('body'); $$renderer.push('body');
}); });
b.global.css.add({ hash: 'h', code: 'c' }); b.global.css.add({ hash: 'h', code: 'c' });
b.global.set_title('Title', [1]); b.global.set_title('Title', [1]);
@ -140,14 +140,14 @@ test('subsume replaces tree content and state from other', () => {
}); });
test('subsume refuses to switch modes', () => { test('subsume refuses to switch modes', () => {
const a = new Payload(new SSRState('sync'), undefined, 'head'); const a = new Renderer(new SSRState('sync'), undefined, 'head');
a.push('<meta />'); a.push('<meta />');
a.local.select_value = 'A'; a.local.select_value = 'A';
const b = new Payload(new SSRState('async')); const b = new Renderer(new SSRState('async'));
b.child(async ($$payload) => { b.child(async ($$renderer) => {
await Promise.resolve(); await Promise.resolve();
$$payload.push('body'); $$renderer.push('body');
}); });
b.global.css.add({ hash: 'h', code: 'c' }); b.global.css.add({ hash: 'h', code: 'c' });
b.global.set_title('Title', [1]); b.global.set_title('Title', [1]);
@ -155,7 +155,7 @@ test('subsume refuses to switch modes', () => {
b.promises.initial = Promise.resolve(); b.promises.initial = Promise.resolve();
expect(() => a.subsume(b)).toThrow( expect(() => a.subsume(b)).toThrow(
"invariant: A payload cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!" "invariant: A renderer cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!"
); );
}); });
@ -200,31 +200,31 @@ describe('async', () => {
disable_async_mode_flag(); disable_async_mode_flag();
}); });
test('awaiting payload gets async content', async () => { test('awaiting renderer gets async content', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('1'); renderer.push('1');
payload.child(async ($$payload) => { renderer.child(async ($$renderer) => {
await Promise.resolve(); await Promise.resolve();
$$payload.push('2'); $$renderer.push('2');
}); });
payload.push('3'); renderer.push('3');
}; };
const result = await Payload.render(component as unknown as Component); const result = await Renderer.render(component as unknown as Component);
expect(result.head).toBe(''); expect(result.head).toBe('');
expect(result.body).toBe('<!--[--><!--[-->123<!--]--><!--]-->'); expect(result.body).toBe('<!--[--><!--[-->123<!--]--><!--]-->');
expect(() => result.html).toThrow('html_deprecated'); expect(() => result.html).toThrow('html_deprecated');
}); });
test('compact schedules followup when compaction input is async', async () => { test('compact schedules followup when compaction input is async', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.child(async ($$payload) => { renderer.child(async ($$renderer) => {
await Promise.resolve(); await Promise.resolve();
$$payload.push('X'); $$renderer.push('X');
}); });
payload.push('b'); renderer.push('b');
payload.compact({ renderer.compact({
start: 0, start: 0,
fn: async (content) => ({ fn: async (content) => ({
body: content.body.toLowerCase(), body: content.body.toLowerCase(),
@ -233,132 +233,132 @@ describe('async', () => {
}); });
}; };
const { body, head } = await Payload.render(component as unknown as Component); const { body, head } = await Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->axb<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->axb<!--]--><!--]-->');
}); });
test('push accepts async functions in async context', async () => { test('push accepts async functions in async context', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.push(async () => { renderer.push(async () => {
await Promise.resolve(); await Promise.resolve();
return 'b'; return 'b';
}); });
payload.push('c'); renderer.push('c');
}; };
const { head, body } = await Payload.render(component as unknown as Component); const { head, body } = await Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->abc<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->abc<!--]--><!--]-->');
}); });
test('push handles async functions with different timing', async () => { test('push handles async functions with different timing', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push(async () => { renderer.push(async () => {
await Promise.resolve(); await Promise.resolve();
return 'fast'; return 'fast';
}); });
payload.push(async () => { renderer.push(async () => {
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
return 'slow'; return 'slow';
}); });
payload.push('sync'); renderer.push('sync');
}; };
const { head, body } = await Payload.render(component as unknown as Component); const { head, body } = await Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->fastslowsync<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->fastslowsync<!--]--><!--]-->');
}); });
test('push async functions work with head content type', async () => { test('push async functions work with head content type', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.child(($$payload) => { renderer.child(($$renderer) => {
$$payload.push(async () => { $$renderer.push(async () => {
await Promise.resolve(); await Promise.resolve();
return '<title>Async Title</title>'; return '<title>Async Title</title>';
}); });
}, 'head'); }, 'head');
}; };
const { head, body } = await Payload.render(component as unknown as Component); const { head, body } = await Renderer.render(component as unknown as Component);
expect(body).toBe('<!--[--><!--[--><!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[--><!--]--><!--]-->');
expect(head).toBe('<title>Async Title</title>'); expect(head).toBe('<title>Async Title</title>');
}); });
test('push async functions can be mixed with child payloads', async () => { test('push async functions can be mixed with child renderers', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('start-'); renderer.push('start-');
payload.push(async () => { renderer.push(async () => {
await Promise.resolve(); await Promise.resolve();
return 'async-'; return 'async-';
}); });
payload.child(($$payload) => { renderer.child(($$renderer) => {
$$payload.push('child-'); $$renderer.push('child-');
}); });
payload.push('-end'); renderer.push('-end');
}; };
const { head, body } = await Payload.render(component as unknown as Component); const { head, body } = await Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->start-async-child--end<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->start-async-child--end<!--]--><!--]-->');
}); });
test('push async functions work with compact operations', async () => { test('push async functions work with compact operations', async () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.push(async () => { renderer.push(async () => {
await Promise.resolve(); await Promise.resolve();
return 'b'; return 'b';
}); });
payload.push('c'); renderer.push('c');
payload.compact({ renderer.compact({
start: 0, start: 0,
fn: (content) => ({ head: '', body: content.body.toUpperCase() }) fn: (content) => ({ head: '', body: content.body.toUpperCase() })
}); });
}; };
const { head, body } = await Payload.render(component as unknown as Component); const { head, body } = await Renderer.render(component as unknown as Component);
expect(head).toBe(''); expect(head).toBe('');
expect(body).toBe('<!--[--><!--[-->ABC<!--]--><!--]-->'); expect(body).toBe('<!--[--><!--[-->ABC<!--]--><!--]-->');
}); });
test('push async functions are not supported in sync context', () => { test('push async functions are not supported in sync context', () => {
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.push('a'); renderer.push('a');
payload.push(() => Promise.resolve('b')); renderer.push(() => Promise.resolve('b'));
}; };
expect(() => Payload.render(component as unknown as Component).body).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).body).toThrow('await_invalid');
expect(() => Payload.render(component as unknown as Component).html).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).html).toThrow('await_invalid');
expect(() => Payload.render(component as unknown as Component).head).toThrow('await_invalid'); expect(() => Renderer.render(component as unknown as Component).head).toThrow('await_invalid');
}); });
test('on_destroy yields callbacks in the correct order', async () => { test('on_destroy yields callbacks in the correct order', async () => {
const destroyed: string[] = []; const destroyed: string[] = [];
const component = (payload: Payload) => { const component = (renderer: Renderer) => {
payload.component((payload) => { renderer.component((renderer) => {
payload.on_destroy(() => destroyed.push('a')); renderer.on_destroy(() => destroyed.push('a'));
// children should not alter relative order // children should not alter relative order
payload.child(async (payload) => { renderer.child(async (renderer) => {
await Promise.resolve(); await Promise.resolve();
payload.on_destroy(() => destroyed.push('b')); renderer.on_destroy(() => destroyed.push('b'));
payload.on_destroy(() => destroyed.push('b*')); renderer.on_destroy(() => destroyed.push('b*'));
}); });
// but child components should // but child components should
payload.component((payload) => { renderer.component((renderer) => {
payload.on_destroy(() => destroyed.push('c')); renderer.on_destroy(() => destroyed.push('c'));
}); });
payload.child((payload) => { renderer.child((renderer) => {
payload.on_destroy(() => destroyed.push('d')); renderer.on_destroy(() => destroyed.push('d'));
}); });
payload.component((payload) => { renderer.component((renderer) => {
payload.on_destroy(() => destroyed.push('e')); renderer.on_destroy(() => destroyed.push('e'));
}); });
}); });
}; };
await Payload.render(component as unknown as Component); await Renderer.render(component as unknown as Component);
expect(destroyed).toEqual(['c', 'e', 'a', 'b', 'b*', 'd']); expect(destroyed).toEqual(['c', 'e', 'a', 'b', 'b*', 'd']);
}); });
}); });

@ -1,13 +1,13 @@
import type { Element } from './dev'; import type { Element } from './dev';
import type { Payload } from './payload'; import type { Renderer } from './renderer';
export interface SSRContext { export interface SSRContext {
/** parent */ /** parent */
p: null | SSRContext; p: null | SSRContext;
/** component context */ /** component context */
c: null | Map<unknown, unknown>; c: null | Map<unknown, unknown>;
/** payload (renderer) */ /** renderer */
r: null | Payload; r: null | Renderer;
/** dev mode only: the current component function */ /** dev mode only: the current component function */
function?: any; function?: any;
/** dev mode only: the current element */ /** dev mode only: the current element */

@ -1,25 +1,25 @@
import 'svelte/internal/flags/async'; import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Async_each_fallback_hoisting($$payload) { export default function Async_each_fallback_hoisting( $$renderer) {
$$payload.child(async ($$payload) => { $$renderer.child(async ( $$renderer) => {
const each_array = $.ensure_array_like(await Promise.resolve([])); const each_array = $.ensure_array_like(await Promise.resolve([]));
if (each_array.length !== 0) { if (each_array.length !== 0) {
$$payload.push('<!--[-->'); $$renderer.push('<!--[-->');
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index]; let item = each_array[$$index];
$$payload.push(`<!---->`); $$renderer.push(`<!---->`);
$$payload.push(async () => $.escape(await Promise.reject('This should never be reached'))); $$renderer.push(async () => $.escape(await Promise.reject('This should never be reached')));
} }
} else { } else {
$$payload.push('<!--[!-->'); $$renderer.push('<!--[!-->');
$$payload.push(`<!---->`); $$renderer.push(`<!---->`);
$$payload.push(async () => $.escape(await Promise.resolve(4))); $$renderer.push(async () => $.escape(await Promise.resolve(4)));
} }
}); });
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,23 +1,23 @@
import 'svelte/internal/flags/async'; import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Async_each_hoisting($$payload) { export default function Async_each_hoisting( $$renderer) {
const first = Promise.resolve(1); const first = Promise.resolve(1);
const second = Promise.resolve(2); const second = Promise.resolve(2);
const third = Promise.resolve(3); const third = Promise.resolve(3);
$$payload.push(`<!--[-->`); $$renderer.push(`<!--[-->`);
$$payload.child(async ($$payload) => { $$renderer.child(async ( $$renderer) => {
const each_array = $.ensure_array_like(await Promise.resolve([first, second, third])); const each_array = $.ensure_array_like(await Promise.resolve([first, second, third]));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index]; let item = each_array[$$index];
$$payload.push(`<!---->`); $$renderer.push(`<!---->`);
$$payload.push(async () => $.escape(await item)); $$renderer.push(async () => $.escape(await item));
} }
}); });
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,16 +1,16 @@
import 'svelte/internal/flags/async'; import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Async_if_alternate_hoisting($$payload) { export default function Async_if_alternate_hoisting( $$renderer) {
$$payload.child(async ($$payload) => { $$renderer.child(async ( $$renderer) => {
if (await Promise.resolve(false)) { if (await Promise.resolve(false)) {
$$payload.push('<!--[-->'); $$renderer.push('<!--[-->');
$$payload.push(async () => $.escape(await Promise.reject('no no no'))); $$renderer.push(async () => $.escape(await Promise.reject('no no no')));
} else { } else {
$$payload.push('<!--[!-->'); $$renderer.push('<!--[!-->');
$$payload.push(async () => $.escape(await Promise.resolve('yes yes yes'))); $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
} }
}); });
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,16 +1,16 @@
import 'svelte/internal/flags/async'; import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Async_if_hoisting($$payload) { export default function Async_if_hoisting( $$renderer) {
$$payload.child(async ($$payload) => { $$renderer.child(async ( $$renderer) => {
if (await Promise.resolve(true)) { if (await Promise.resolve(true)) {
$$payload.push('<!--[-->'); $$renderer.push('<!--[-->');
$$payload.push(async () => $.escape(await Promise.resolve('yes yes yes'))); $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));
} else { } else {
$$payload.push('<!--[!-->'); $$renderer.push('<!--[!-->');
$$payload.push(async () => $.escape(await Promise.reject('no no no'))); $$renderer.push(async () => $.escape(await Promise.reject('no no no')));
} }
}); });
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,6 +1,6 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Await_block_scope($$payload) { export default function Await_block_scope( $$renderer) {
let counter = { count: 0 }; let counter = { count: 0 };
const promise = Promise.resolve(counter); const promise = Promise.resolve(counter);
@ -8,7 +8,7 @@ export default function Await_block_scope($$payload) {
counter.count += 1; counter.count += 1;
} }
$$payload.push(`<button>clicks: ${$.escape(counter.count)}</button> `); $$renderer.push(`<button>clicks: ${$.escape(counter.count)}</button> `);
$.await($$payload, promise, () => {}, (counter) => {}); $.await( $$renderer, promise, () => {}, (counter) => {});
$$payload.push(`<!--]--> ${$.escape(counter.count)}`); $$renderer.push(`<!--]--> ${$.escape(counter.count)}`);
} }

@ -1,18 +1,18 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
import TextInput from './Child.svelte'; import TextInput from './Child.svelte';
function snippet($$payload) { function snippet( $$renderer) {
$$payload.push(`<!---->Something`); $$renderer.push(`<!---->Something`);
} }
export default function Bind_component_snippet($$payload) { export default function Bind_component_snippet( $$renderer) {
let value = ''; let value = '';
const _snippet = snippet; const _snippet = snippet;
let $$settled = true; let $$settled = true;
let $$inner_payload; let $$inner_renderer;
function $$render_inner($$payload) { function $$render_inner( $$renderer) {
TextInput($$payload, { TextInput( $$renderer, {
get value() { get value() {
return value; return value;
}, },
@ -23,14 +23,14 @@ export default function Bind_component_snippet($$payload) {
} }
}); });
$$payload.push(`<!----> value: ${$.escape(value)}`); $$renderer.push(`<!----> value: ${$.escape(value)}`);
} }
do { do {
$$settled = true; $$settled = true;
$$inner_payload = $$payload.copy(); $$inner_renderer = $$renderer.copy();
$$render_inner($$inner_payload); $$render_inner($$inner_renderer);
} while (!$$settled); } while (!$$settled);
$$payload.subsume($$inner_payload); $$renderer.subsume($$inner_renderer);
} }

@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Bind_this($$payload) { export default function Bind_this( $$renderer) {
Foo($$payload, {}); Foo( $$renderer, {});
} }

@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Class_state_field_constructor_assignment($$payload, $$props) { export default function Class_state_field_constructor_assignment( $$renderer, $$props) {
$$payload.component(($$payload) => { $$renderer.component(( $$renderer) => {
class Foo { class Foo {
a = 0; a = 0;
#b; #b;

@ -1,13 +1,13 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Delegated_locally_declared_shadowed($$payload) { export default function Delegated_locally_declared_shadowed( $$renderer) {
$$payload.push(`<!--[-->`); $$renderer.push(`<!--[-->`);
const each_array = $.ensure_array_like({ length: 1 }); const each_array = $.ensure_array_like({ length: 1 });
for (let index = 0, $$length = each_array.length; index < $$length; index++) { for (let index = 0, $$length = each_array.length; index < $$length; index++) {
$$payload.push(`<button type="button"${$.attr('data-index', index)}>B</button>`); $$renderer.push(`<button type="button"${$.attr('data-index', index)}>B</button>`);
} }
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,10 +1,10 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Main($$payload) { export default function Main( $$renderer) {
// needs to be a snapshot test because jsdom does auto-correct the attribute casing // needs to be a snapshot test because jsdom does auto-correct the attribute casing
let x = 'test'; let x = 'test';
let y = () => 'test'; let y = () => 'test';
$$payload.push(`<div${$.attr('foobar', x)}></div> <svg${$.attr('viewBox', x)}></svg> <custom-element${$.attr('foobar', x)}></custom-element> <div${$.attr('foobar', y())}></div> <svg${$.attr('viewBox', y())}></svg> <custom-element${$.attr('foobar', y())}></custom-element>`); $$renderer.push(`<div${$.attr('foobar', x)}></div> <svg${$.attr('viewBox', x)}></svg> <custom-element${$.attr('foobar', x)}></custom-element> <div${$.attr('foobar', y())}></div> <svg${$.attr('viewBox', y())}></svg> <custom-element${$.attr('foobar', y())}></custom-element>`);
} }

@ -1,13 +1,13 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Each_index_non_null($$payload) { export default function Each_index_non_null( $$renderer) {
$$payload.push(`<!--[-->`); $$renderer.push(`<!--[-->`);
const each_array = $.ensure_array_like(Array(10)); const each_array = $.ensure_array_like(Array(10));
for (let i = 0, $$length = each_array.length; i < $$length; i++) { for (let i = 0, $$length = each_array.length; i < $$length; i++) {
$$payload.push(`<p>index: ${$.escape(i)}</p>`); $$renderer.push(`<p>index: ${$.escape(i)}</p>`);
} }
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,15 +1,15 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Each_string_template($$payload) { export default function Each_string_template( $$renderer) {
$$payload.push(`<!--[-->`); $$renderer.push(`<!--[-->`);
const each_array = $.ensure_array_like(['foo', 'bar', 'baz']); const each_array = $.ensure_array_like(['foo', 'bar', 'baz']);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let thing = each_array[$$index]; let thing = each_array[$$index];
$$payload.push(`<!---->${$.escape(thing)}, `); $$renderer.push(`<!---->${$.escape(thing)}, `);
} }
$$payload.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
} }

@ -1,6 +1,6 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Function_prop_no_getter($$payload) { export default function Function_prop_no_getter( $$renderer) {
let count = 0; let count = 0;
function onmouseup() { function onmouseup() {
@ -9,13 +9,13 @@ export default function Function_prop_no_getter($$payload) {
const plusOne = (num) => num + 1; const plusOne = (num) => num + 1;
Button($$payload, { Button( $$renderer, {
onmousedown: () => count += 1, onmousedown: () => count += 1,
onmouseup, onmouseup,
onmouseenter: () => count = plusOne(count), onmouseenter: () => count = plusOne(count),
children: ($$payload) => { children: ( $$renderer) => {
$$payload.push(`<!---->clicks: ${$.escape(count)}`); $$renderer.push(`<!---->clicks: ${$.escape(count)}`);
}, },
$$slots: { default: true } $$slots: { default: true }

@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Functional_templating($$payload) { export default function Functional_templating( $$renderer) {
$$payload.push(`<h1>hello</h1> <div class="potato"><p>child element</p> <p>another child element</p></div>`); $$renderer.push(`<h1>hello</h1> <div class="potato"><p>child element</p> <p>another child element</p></div>`);
} }

@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Hello_world($$payload) { export default function Hello_world( $$renderer) {
$$payload.push(`<h1>hello world</h1>`); $$renderer.push(`<h1>hello world</h1>`);
} }

@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Hmr($$payload) { export default function Hmr( $$renderer) {
$$payload.push(`<h1>hello world</h1>`); $$renderer.push(`<h1>hello world</h1>`);
} }

@ -1,4 +1,4 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
import { random } from './module.svelte'; import { random } from './module.svelte';
export default function Imports_in_modules($$payload) {} export default function Imports_in_modules( $$renderer) {}

@ -1,8 +1,8 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Nullish_coallescence_omittance($$payload) { export default function Nullish_coallescence_omittance( $$renderer) {
let name = 'world'; let name = 'world';
let count = 0; let count = 0;
$$payload.push(`<h1>Hello, world!</h1> <b>123</b> <button>Count is ${$.escape(count)}</button> <h1>Hello, world</h1>`); $$renderer.push(`<h1>Hello, world!</h1> <b>123</b> <button>Count is ${$.escape(count)}</button> <h1>Hello, world</h1>`);
} }

@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Props_identifier($$payload, $$props) { export default function Props_identifier( $$renderer, $$props) {
$$payload.component(($$payload) => { $$renderer.component(( $$renderer) => {
let { $$slots, $$events, ...props } = $$props; let { $$slots, $$events, ...props } = $$props;
props.a; props.a;

@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Purity($$payload) { export default function Purity( $$renderer) {
$$payload.push(`<p>0</p> <p>${$.escape(location.href)}</p> `); $$renderer.push(`<p>0</p> <p>${$.escape(location.href)}</p> `);
Child($$payload, { prop: encodeURIComponent('hello') }); Child( $$renderer, { prop: encodeURIComponent('hello') });
$$payload.push(`<!---->`); $$renderer.push(`<!---->`);
} }

@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Skip_static_subtree($$payload, $$props) { export default function Skip_static_subtree( $$renderer, $$props) {
let { title, content } = $$props; let { title, content } = $$props;
$$payload.push(`<header><nav><a href="/">Home</a> <a href="/away">Away</a></nav></header> <main><h1>${$.escape(title)}</h1> <div class="static"><p>we don't need to traverse these nodes</p></div> <p>or</p> <p>these</p> <p>ones</p> ${$.html(content)} <p>these</p> <p>trailing</p> <p>nodes</p> <p>can</p> <p>be</p> <p>completely</p> <p>ignored</p></main> <cant-skip><custom-elements with="attributes"></custom-elements></cant-skip> <div><input autofocus/></div> <div><source muted/></div> <select><option value="a"${$.maybe_selected($$payload, 'a')}>a</option></select> <img src="..." alt="" loading="lazy"/> <div><img src="..." alt="" loading="lazy"/></div>`); $$renderer.push(`<header><nav><a href="/">Home</a> <a href="/away">Away</a></nav></header> <main><h1>${$.escape(title)}</h1> <div class="static"><p>we don't need to traverse these nodes</p></div> <p>or</p> <p>these</p> <p>ones</p> ${$.html(content)} <p>these</p> <p>trailing</p> <p>nodes</p> <p>can</p> <p>be</p> <p>completely</p> <p>ignored</p></main> <cant-skip><custom-elements with="attributes"></custom-elements></cant-skip> <div><input autofocus/></div> <div><source muted/></div> <select><option value="a"${$.maybe_selected( $$renderer, 'a')}>a</option></select> <img src="..." alt="" loading="lazy"/> <div><img src="..." alt="" loading="lazy"/></div>`);
} }

@ -1,6 +1,6 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function State_proxy_literal($$payload) { export default function State_proxy_literal( $$renderer) {
let str = ''; let str = '';
let tpl = ``; let tpl = ``;
@ -11,5 +11,5 @@ export default function State_proxy_literal($$payload) {
tpl = ``; tpl = ``;
} }
$$payload.push(`<input${$.attr('value', str)}/> <input${$.attr('value', tpl)}/> <button>reset</button>`); $$renderer.push(`<input${$.attr('value', str)}/> <input${$.attr('value', tpl)}/> <button>reset</button>`);
} }

@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Svelte_element($$payload, $$props) { export default function Svelte_element( $$renderer, $$props) {
let { tag = 'hr' } = $$props; let { tag = 'hr' } = $$props;
$.element($$payload, tag); $.element( $$renderer, tag);
} }

@ -1,6 +1,6 @@
import * as $ from 'svelte/internal/server'; import * as $ from 'svelte/internal/server';
export default function Text_nodes_deriveds($$payload) { export default function Text_nodes_deriveds( $$renderer) {
let count1 = 0; let count1 = 0;
let count2 = 0; let count2 = 0;
@ -12,5 +12,5 @@ export default function Text_nodes_deriveds($$payload) {
return count2; return count2;
} }
$$payload.push(`<p>${$.escape(text1())}${$.escape(text2())}</p>`); $$renderer.push(`<p>${$.escape(text1())}${$.escape(text2())}</p>`);
} }
Loading…
Cancel
Save