feat: First pass at payload

adjust-boundary-error-message
S. Elliott Johnson 1 month ago
parent 28403beaeb
commit d7f5d86317

@ -68,7 +68,7 @@ export let on_destroy = [];
*/ */
export function render(component, options = {}) { export function render(component, options = {}) {
try { 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; const prev_on_destroy = on_destroy;
on_destroy = []; on_destroy = [];
@ -107,7 +107,7 @@ export function render(component, options = {}) {
head += `<style id="${hash}">${code}</style>`; head += `<style id="${hash}">${code}</style>`;
} }
const body = payload.out.join(''); const body = payload.collect();
return { return {
head, head,
@ -569,6 +569,9 @@ export function valueless_option(payload, children) {
if (body.replace(/<!---->/g, '') === payload.select_value) { if (body.replace(/<!---->/g, '') === payload.select_value) {
// replace '>' with ' selected>' (closing tag will be added later) // replace '>' with ' selected>' (closing tag will be added later)
var last_item = payload.out[i - 1]; 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>'; 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 // Remove the old items after position i and add the body as a single item
payload.out.splice(i, payload.out.length - i, body); payload.out.splice(i, payload.out.length - i, body);

@ -1,3 +1,5 @@
import { deferred } from '../shared/utils';
export class HeadPayload { export class HeadPayload {
/** @type {Set<{ hash: string; code: string }>} */ /** @type {Set<{ hash: string; code: string }>} */
css = new Set(); css = new Set();
@ -16,17 +18,109 @@ export class HeadPayload {
export class Payload { export class Payload {
/** @type {Set<{ hash: string; code: string }>} */ /** @type {Set<{ hash: string; code: string }>} */
css = new Set(); css;
/** @type {string[]} */ /** @type {(string | ChildPayload)[]} */
out = []; out = [];
uid = () => ''; /** @type {() => string} */
uid;
/** @type {string | undefined} */
select_value = undefined; select_value = undefined;
/** @type {HeadPayload} */
head;
/** @type {'sync' | 'async'} */
mode;
/** @type {Promise<string>[]} */
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;
}
head = new HeadPayload(); /**
* 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<void> }} 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());
}
constructor(id_prefix = '') { /**
this.uid = props_id_generator(id_prefix); * Waits for all child payloads to finish their blocking asynchronous work, then returns the generated HTML.
this.head.uid = this.uid; * @returns {Promise<string>}
*/
async collect_async() {
// TODO throw in `sync` mode
/** @type {Promise<void>[]} */
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;
}
}
class ChildPayload extends Payload {
deferred = /** @type {ReturnType<typeof deferred<void>>} */ (deferred());
/**
* @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);
} }
} }

Loading…
Cancel
Save