adjust-boundary-error-message
S. Elliott Johnson 1 week ago
parent e39b4b7546
commit 51392be8ae

@ -40,10 +40,10 @@ function print_error(payload, message) {
// eslint-disable-next-line no-console
console.error(message);
payload.out.push({
type: 'head',
content: `<script>console.error(${JSON.stringify(message)})</script>`
});
payload.child(
(payload) => payload.push(`<script>console.error(${JSON.stringify(message)})</script>`),
'head'
);
}
export function reset_elements() {

@ -191,9 +191,11 @@ export async function render_async(component, options = {}) {
* @returns {void}
*/
export function head(payload, fn) {
payload.out.push({ type: 'head', content: BLOCK_OPEN });
payload.child(fn, 'head');
payload.out.push({ type: 'head', content: BLOCK_CLOSE });
payload.child((payload) => {
payload.push(BLOCK_OPEN);
payload.child(fn);
payload.push(BLOCK_CLOSE);
}, 'head');
}
/**

@ -1,22 +1,21 @@
/** @typedef {{ type: 'head' | 'body', content: string }} TNode */
/** @typedef {{ [key in TNode['type']]: string }} AccumulatedContent */
/** @typedef {'head' | 'body'} PayloadType */
/** @typedef {{ [key in PayloadType]: string }} AccumulatedContent */
/** @typedef {{ start: number, end: number, fn: (content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent> }} Compaction */
// TODO we test for `instanceof AsyncContentTree` in some tight loops -- we might optimize
// by giving the tree a symbol property and checking that instead if we can actually notice any impact
/**
* /**
* A base class for payloads. Payloads are basically a tree of `string | Payload`s, where each
* `Payload` in the tree represents work that may or may not have completed. A payload 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 performing asynchronous work. A payload can also be collected
* asynchronously with {@link collect_async}, which will wait for all children to complete before
* collecting their contents.
* Payloads are basically a tree of `string | Payload`s, where each `Payload` in the tree represents
* work that may or may not have completed. A payload 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
* performing asynchronous work. A payload can also be collected asynchronously with
* {@link collect_async}, which will wait for all children to complete before collecting their
* contents.
*
* The `string` values within a payload are always associated with the {@link type} of that payload. To switch types,
* call {@link child} with a different `type` argument.
*/
export class Payload {
/**
* @type {TNode['type']}
* @type {PayloadType}
*/
type;
@ -25,7 +24,7 @@ export class Payload {
/**
* The contents of the payload.
* @type {(TNode | Payload)[]}
* @type {(string | Payload)[]}
*/
out = [];
@ -55,7 +54,7 @@ export class Payload {
* @param {TreeState} [global]
* @param {{ select_value: string | undefined }} [local]
* @param {Payload | undefined} [parent]
* @param {TNode['type']} [type]
* @param {PayloadType} [type]
*/
constructor(global = new TreeState(), local = { select_value: undefined }, parent, type) {
this.global = global;
@ -69,7 +68,7 @@ export class Payload {
* but has its own `out` array and `promise` property. The child payload is automatically
* inserted into the parent payload's `out` array.
* @param {(tree: Payload) => void | Promise<void>} render
* @param {TNode['type']} [type]
* @param {PayloadType} [type]
* @returns {void}
*/
child(render, type) {
@ -81,13 +80,9 @@ export class Payload {
}
}
/**
* This is a convenience function that allows pushing strings, and will automatically use the configured `type` of this
* payload. It's fine to push content of a different type to the `out` array; it's just more annoying to write.
* @param {string} content
*/
/** @param {string} content */
push(content) {
this.out.push({ type: this.type, content });
this.out.push(content);
}
/**
@ -100,25 +95,24 @@ export class Payload {
const to_compact = this.out.splice(start, end - start, child);
const promises = Payload.#collect_promises(to_compact, []);
/** @param {AccumulatedContent | Promise<AccumulatedContent>} res */
const push_result = (res) => {
const push_result = () => {
const res = fn(Payload.#collect_content(to_compact, this.type));
if (res instanceof Promise) {
child.promise = res.then((resolved) => {
const promise = res.then((resolved) => {
Payload.#push_accumulated_content(child, resolved);
});
return promise;
} else {
Payload.#push_accumulated_content(child, res);
}
};
if (promises.length > 0) {
// we have to wait for the accumulated work associated with all branches to complete,
// we have to wait for the accumulated work associated with all pruned branches to complete,
// then we can accumulate their content to compact it.
child.promise = Promise.all(promises)
.then(() => fn(Payload.#collect_content(to_compact)))
.then(push_result);
child.promise = Promise.all(promises).then(push_result);
} else {
push_result(fn(Payload.#collect_content(to_compact)));
push_result();
}
}
@ -136,26 +130,27 @@ export class Payload {
async collect_async() {
// TODO: Should probably use `Promise.allSettled` here just so we can report detailed errors
await Promise.all(Payload.#collect_promises(this.out, this.promise ? [this.promise] : []));
return Payload.#collect_content(this.out);
return Payload.#collect_content(this.out, this.type);
}
/**
* 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. Throws if any of the children are
* performing asynchronous work.
* @returns {AccumulatedContent}
*/
collect() {
const promises = Payload.#collect_promises(this.out, this.promise ? [this.promise] : []);
if (promises.length > 0) {
// TODO is there a good way to report where this is? Probably by using some sort of loc or stack trace in `child` creation
// TODO is there a good way to report where this is? Probably by using some sort of loc or stack trace in `child` creation.
throw new Error('Encountered an asynchronous component while rendering synchronously');
}
return Payload.#collect_content(this.out);
return Payload.#collect_content(this.out, this.type);
}
copy() {
const copy = new Payload(this.global, this.local, this.parent, this.type);
copy.out = this.out.map((item) => (item instanceof Payload ? item.copy() : item));
copy.out = this.out.map((item) => (typeof item === 'string' ? item : item.copy()));
copy.promise = this.promise;
return copy;
}
@ -167,7 +162,7 @@ export class Payload {
this.global.subsume(other.global);
this.local = other.local;
this.out = other.out.map((item) => {
if (item instanceof Payload) {
if (typeof item !== 'string') {
item.subsume(item);
}
return item;
@ -177,34 +172,34 @@ export class Payload {
}
/**
* @param {(TNode | Payload)[]} items
* @param {(string | Payload)[]} items
* @param {Promise<void>[]} promises
* @returns {Promise<void>[]}
*/
static #collect_promises(items, promises) {
for (const item of items) {
if (item instanceof Payload) {
if (item.promise) {
promises.push(item.promise);
}
Payload.#collect_promises(item.out, promises);
if (typeof item === 'string') continue;
if (item.promise) {
promises.push(item.promise);
}
Payload.#collect_promises(item.out, promises);
}
return promises;
}
/**
* Collect all of the code from the `out` array and return it as a string.
* @param {(TNode | Payload)[]} items
* @param {(string | Payload)[]} items
* @param {PayloadType} current_type
* @param {AccumulatedContent} content
* @returns {AccumulatedContent}
*/
static #collect_content(items, content = { head: '', body: '' }) {
static #collect_content(items, current_type, content = { head: '', body: '' }) {
for (const item of items) {
if (item instanceof Payload) {
Payload.#collect_content(item.out, content);
if (typeof item === 'string') {
content[current_type] += item;
} else {
content[item.type] += item.content;
Payload.#collect_content(item.out, item.type, content);
}
}
return content;
@ -216,7 +211,10 @@ export class Payload {
*/
static #push_accumulated_content(tree, accumulated_content) {
for (const [type, content] of Object.entries(accumulated_content)) {
tree.out.push({ type: /** @type {TNode['type']} */ (type), content });
if (!content) continue;
const child = new Payload(tree.global, tree.local, tree, /** @type {PayloadType} */ (type));
child.push(content);
tree.out.push(child);
}
}
}

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({ load_compiled: true });
Loading…
Cancel
Save