adjust-boundary-error-message
S. Elliott Johnson 2 weeks ago
parent 0cb62125c1
commit 866a11cf19

@ -10,6 +10,7 @@ export interface AnalysisState {
ast_type: 'instance' | 'template' | 'module';
fragment: AST.Fragment | null;
snippet: AST.SnippetBlock | null;
boundary: AST.SvelteBoundary | null;
/**
* Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root.
* Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between.

@ -37,9 +37,11 @@ export function AwaitExpression(node, context) {
if (!context.state.analysis.runes) {
e.legacy_await_invalid(node);
}
context.state.analysis.suspends = true;
}
// the await will only block if there's no `pending` snippet
context.state.analysis.has_blocking_await ||=
suspend && !context.state.boundary?.metadata.pending;
context.next();
}

@ -14,6 +14,10 @@ export function SvelteBoundary(node, context) {
e.svelte_boundary_invalid_attribute(attribute);
}
if (attribute.name === 'pending') {
node.metadata.pending = attribute;
}
if (
attribute.value === true ||
(Array.isArray(attribute.value) &&
@ -23,5 +27,12 @@ export function SvelteBoundary(node, context) {
}
}
context.next();
node.metadata.pending ??=
/** @type {AST.SnippetBlock | undefined} */ (
node.fragment.nodes.find(
(node) => node.type === 'SnippetBlock' && node.expression.name === 'pending'
)
) ?? null;
context.next({ ...context.state, boundary: node });
}

@ -247,7 +247,7 @@ export function server_component(analysis, options) {
.../** @type {Statement[]} */ (instance.body),
.../** @type {Statement[]} */ (template.body)
]),
analysis.suspends
analysis.has_blocking_await
)
)
)

@ -12,21 +12,11 @@ import { build_attribute_value } from './shared/utils.js';
export function SvelteBoundary(node, context) {
context.state.template.push(b.literal(BLOCK_OPEN));
// if this has a `pending` snippet, render it
const pending_attribute = /** @type {AST.Attribute} */ (
node.attributes.find((node) => node.type === 'Attribute' && node.name === 'pending')
);
const pending_snippet = /** @type {AST.SnippetBlock} */ (
node.fragment.nodes.find(
(node) => node.type === 'SnippetBlock' && node.expression.name === 'pending'
)
);
if (pending_attribute) {
const value = build_attribute_value(pending_attribute.value, context, false, true);
const pending_snippet = node.metadata.pending;
if (pending_snippet?.type === 'Attribute') {
const value = build_attribute_value(pending_snippet.value, context, false, true);
context.state.template.push(b.call(value, b.id('$$payload')));
} else if (pending_snippet) {
} else if (pending_snippet?.type === 'SnippetBlock') {
context.state.template.push(
/** @type {BlockStatement} */ (context.visit(pending_snippet.body))
);

@ -106,8 +106,8 @@ export interface ComponentAnalysis extends Analysis {
* Every snippet that is declared locally
*/
snippets: Set<AST.SnippetBlock>;
/** Whether the component uses `await` in a context that would cause suspense. */
suspends: boolean;
/** Whether the component uses `await` in a context that would require an `await` on the server. */
has_blocking_await: boolean;
}
declare module 'estree' {

@ -401,6 +401,10 @@ export namespace AST {
export interface SvelteBoundary extends BaseElement {
type: 'SvelteBoundary';
name: 'svelte:boundary';
/** @internal */
metadata: {
pending: SnippetBlock | Attribute | null;
};
}
export interface SvelteHead extends BaseElement {

@ -564,7 +564,9 @@ export function valueless_option(payload, children) {
children();
var body = payload.out.slice(i).join('');
// TODO this seems really likely to break in async world; we really need to find a better way to do this
// @ts-expect-error
var body = collect_body(payload.out.slice(i));
if (body.replace(/<!---->/g, '') === payload.select_value) {
// replace '>' with ' selected>' (closing tag will be added later)
@ -577,3 +579,19 @@ export function valueless_option(payload, children) {
payload.out.splice(i, payload.out.length - i, body);
}
}
/**
* @param {(string | Payload)[]} out_fragment
* @returns {string}
*/
function collect_body(out_fragment) {
let body = '';
for (const item of out_fragment) {
if (typeof item === 'string') {
body += item;
} else {
body += collect_body(/** @type {(string | Payload)[]} */ (item.out));
}
}
return body;
}

@ -46,7 +46,8 @@ class BasePayload {
* @returns {void}
*/
child(render) {
const child = new BasePayload(this._state);
// @ts-expect-error dynamic constructor invocation for subclass instance creation
const child = new this.constructor(this._state);
this.out.push(child);
const result = render({ $$payload: child });
if (result instanceof Promise) {
@ -85,8 +86,10 @@ class BasePayload {
*/
#collect_promises(items, promises = this.promise ? [this.promise] : []) {
for (const item of items) {
if (item instanceof BasePayload && item.promise) {
promises.push(item.promise);
if (item instanceof BasePayload) {
if (item.promise) {
promises.push(item.promise);
}
this.#collect_promises(item.out, promises);
}
}
@ -204,7 +207,8 @@ export function copy_payload({ out, css, head, uid }) {
uid,
head: new HeadPayload({
css: new Set(head.css),
title: head.title,
// @ts-expect-error
title: head._state.title,
uid: head.uid
})
});

Loading…
Cancel
Save