diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 62ee22d6fc..3064ee88d7 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -68,7 +68,7 @@ export let on_destroy = [];
*/
export function render(component, options = {}) {
try {
- const payload = new Payload(options.idPrefix ? options.idPrefix + '-' : '');
+ const payload = new Payload({ id_prefix: options.idPrefix ? options.idPrefix + '-' : '' });
const prev_on_destroy = on_destroy;
on_destroy = [];
@@ -107,7 +107,7 @@ export function render(component, options = {}) {
head += ``;
}
- const body = payload.out.join('');
+ const body = payload.collect();
return {
head,
@@ -569,6 +569,9 @@ export function valueless_option(payload, children) {
if (body.replace(//g, '') === payload.select_value) {
// replace '>' with ' selected>' (closing tag will be added later)
var last_item = payload.out[i - 1];
+ if (typeof last_item !== 'string') {
+ throw new Error('TODO something very bad has happened, this should be very impossible');
+ }
payload.out[i - 1] = last_item.slice(0, -1) + ' selected>';
// Remove the old items after position i and add the body as a single item
payload.out.splice(i, payload.out.length - i, body);
diff --git a/packages/svelte/src/internal/server/payload.js b/packages/svelte/src/internal/server/payload.js
index 195488e061..315f8af985 100644
--- a/packages/svelte/src/internal/server/payload.js
+++ b/packages/svelte/src/internal/server/payload.js
@@ -1,3 +1,5 @@
+import { deferred } from '../shared/utils';
+
export class HeadPayload {
/** @type {Set<{ hash: string; code: string }>} */
css = new Set();
@@ -16,17 +18,109 @@ export class HeadPayload {
export class Payload {
/** @type {Set<{ hash: string; code: string }>} */
- css = new Set();
- /** @type {string[]} */
+ css;
+ /** @type {(string | ChildPayload)[]} */
out = [];
- uid = () => '';
+ /** @type {() => string} */
+ uid;
+ /** @type {string | undefined} */
select_value = undefined;
+ /** @type {HeadPayload} */
+ head;
+ /** @type {'sync' | 'async'} */
+ mode;
+ /** @type {Promise[]} */
+ tail = [];
+
+ /**
+ * @param {{ id_prefix?: string, mode?: 'sync' | 'async', head?: HeadPayload, uid?: () => string, out?: (string | ChildPayload)[], css?: Set<{ hash: string; code: string }>, select_value?: any }} args
+ */
+ constructor({
+ id_prefix = '',
+ mode = 'sync',
+ head = new HeadPayload(),
+ uid = props_id_generator(id_prefix),
+ css = new Set()
+ } = {}) {
+ this.uid = uid;
+ this.head = head;
+ this.mode = mode;
+ this.css = css;
+ }
+
+ /**
+ * Create a child scope. `front` represents the initial, synchronous code, and `back` represents all code from the first `await` onwards.
+ * Typically a child will be created for each component.
+ * @param {{ front: (args: { payload: Payload }) => void, back: (args: { payload: Payload }) => Promise }} args
+ * @returns {void}
+ */
+ child({ front, back }) {
+ const child = new ChildPayload(this);
+ front({ payload: child });
+ // TODO: boundary stuff? Or does this go inside the `back` function?
+ back({ payload: child }).then(() => child.deferred.resolve());
+ }
+
+ /**
+ * Waits for all child payloads to finish their blocking asynchronous work, then returns the generated HTML.
+ * @returns {Promise}
+ */
+ async collect_async() {
+ // TODO throw in `sync` mode
+ /** @type {Promise[]} */
+ const promises = [];
+
+ /**
+ * @param {(string | ChildPayload)[]} items
+ */
+ function collect_promises(items) {
+ for (const item of items) {
+ if (item instanceof ChildPayload) {
+ promises.push(item.deferred.promise);
+ collect_promises(item.out);
+ }
+ }
+ }
+
+ collect_promises(this.out);
+ await Promise.all(promises);
+ return this.collect();
+ }
+
+ /**
+ * Collect all of the code from the `out` array and return it as a string. If in `async` mode, wait on
+ * `finished` prior to collecting.
+ * @returns {string}
+ */
+ collect() {
+ // TODO throw in `async` mode
+ let html = '';
+ for (const item of this.out) {
+ if (typeof item === 'string') {
+ html += item;
+ } else {
+ html += item.collect();
+ }
+ }
+ return html;
+ }
+}
- head = new HeadPayload();
+class ChildPayload extends Payload {
+ deferred = /** @type {ReturnType>} */ (deferred());
- constructor(id_prefix = '') {
- this.uid = props_id_generator(id_prefix);
- this.head.uid = this.uid;
+ /**
+ * @param {Payload} parent
+ */
+ constructor(parent) {
+ super({
+ mode: parent.mode,
+ head: parent.head,
+ uid: parent.uid,
+ css: parent.css
+ });
+ this.root = parent;
+ parent.out.push(this);
}
}