Rich Harris 8 months ago
parent e6cd4265eb
commit ea139370de

@ -20,6 +20,7 @@ import { ArrowFunctionExpression } from './visitors/ArrowFunctionExpression.js';
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
import { Attribute } from './visitors/Attribute.js';
import { AwaitBlock } from './visitors/AwaitBlock.js';
import { AwaitExpression } from './visitors/AwaitExpression.js';
import { BindDirective } from './visitors/BindDirective.js';
import { CallExpression } from './visitors/CallExpression.js';
import { ClassBody } from './visitors/ClassBody.js';
@ -133,6 +134,7 @@ const visitors = {
AssignmentExpression,
Attribute,
AwaitBlock,
AwaitExpression,
BindDirective,
CallExpression,
ClassBody,

@ -0,0 +1,14 @@
/** @import { AwaitExpression } from 'estree' */
/** @import { Context } from '../types' */
/**
* @param {AwaitExpression} node
* @param {Context} context
*/
export function AwaitExpression(node, context) {
if (context.state.expression) {
context.state.expression.is_async = true;
}
context.next();
}

@ -12,6 +12,7 @@ import { ArrowFunctionExpression } from './visitors/ArrowFunctionExpression.js';
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
import { Attribute } from './visitors/Attribute.js';
import { AwaitBlock } from './visitors/AwaitBlock.js';
import { AwaitExpression } from './visitors/AwaitExpression.js';
import { BinaryExpression } from './visitors/BinaryExpression.js';
import { BindDirective } from './visitors/BindDirective.js';
import { BlockStatement } from './visitors/BlockStatement.js';
@ -87,6 +88,7 @@ const visitors = {
AssignmentExpression,
Attribute,
AwaitBlock,
AwaitExpression,
BinaryExpression,
BindDirective,
BlockStatement,

@ -0,0 +1,16 @@
/** @import { AwaitExpression, Expression } from 'estree' */
/** @import { ComponentContext } from '../types' */
import * as b from '../../../../utils/builders.js';
/**
* @param {AwaitExpression} node
* @param {ComponentContext} context
*/
export function AwaitExpression(node, context) {
return b.await(
b.call(
'$.preserve_context',
node.argument && /** @type {Expression} */ (context.visit(node.argument))
)
);
}

@ -69,7 +69,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
state.template.push(' ');
const { has_state, has_call, value } = build_template_chunk(sequence, visit, state);
const { has_state, has_call, is_async, value } = build_template_chunk(sequence, visit, state);
// if this is a standalone `{expression}`, make sure we handle the case where
// no text node was created because the expression was empty during SSR
@ -79,7 +79,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
const update = b.stmt(b.call('$.set_text', id, value));
if (has_call && !within_bound_contenteditable) {
state.init.push(build_update(update));
state.init.push(build_update(update, is_async));
} else if (has_state && !within_bound_contenteditable) {
state.update.push(update);
} else {

@ -14,7 +14,7 @@ import { locator } from '../../../../../state.js';
* @param {Array<AST.Text | AST.ExpressionTag>} values
* @param {(node: AST.SvelteNode, state: any) => any} visit
* @param {ComponentClientTransformState} state
* @returns {{ value: Expression, has_state: boolean, has_call: boolean }}
* @returns {{ value: Expression, has_state: boolean, has_call: boolean, is_async: boolean }}
*/
export function build_template_chunk(values, visit, state) {
/** @type {Expression[]} */
@ -25,6 +25,7 @@ export function build_template_chunk(values, visit, state) {
let has_call = false;
let has_state = false;
let is_async = false;
let contains_multiple_call_expression = false;
for (const node of values) {
@ -34,6 +35,7 @@ export function build_template_chunk(values, visit, state) {
contains_multiple_call_expression ||= has_call && metadata.has_call;
has_call ||= metadata.has_call;
has_state ||= metadata.has_state;
is_async ||= metadata.is_async;
}
}
@ -68,7 +70,7 @@ export function build_template_chunk(values, visit, state) {
} else if (values.length === 1) {
// If we have a single expression, then pass that in directly to possibly avoid doing
// extra work in the template_effect (instead we do the work in set_text).
return { value: visit(node.expression, state), has_state, has_call };
return { value: visit(node.expression, state), has_state, has_call, is_async };
} else {
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
}
@ -84,17 +86,18 @@ export function build_template_chunk(values, visit, state) {
const value = b.template(quasis, expressions);
return { value, has_state, has_call };
return { value, has_state, has_call, is_async };
}
/**
* @param {Statement} statement
* @param {boolean} is_async
*/
export function build_update(statement) {
export function build_update(statement, is_async) {
const body =
statement.type === 'ExpressionStatement' ? statement.expression : b.block([statement]);
return b.stmt(b.call('$.template_effect', b.thunk(body)));
return b.stmt(b.call('$.template_effect', b.thunk(body, is_async)));
}
/**

@ -58,6 +58,7 @@ export function create_expression_metadata() {
return {
dependencies: new Set(),
has_state: false,
has_call: false
has_call: false,
is_async: false
};
}

@ -318,6 +318,8 @@ export interface ExpressionMetadata {
has_state: boolean;
/** True if the expression involves a call expression (often, it will need to be wrapped in a derived) */
has_call: boolean;
/** True if the expression contains `await` */
is_async: boolean;
}
export * from './template.js';

@ -191,5 +191,3 @@ export {
} from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
export { create_suspense } from './internal/client/dom/blocks/boundary.js';

@ -248,3 +248,30 @@ export function create_suspense() {
return [suspend, unsuspend];
}
/**
* @template T
* @param {Promise<T>} promise
* @returns {Promise<T>}
*/
export async function preserve_context(promise) {
if (!active_effect) {
return promise;
}
var previous_effect = active_effect;
var previous_reaction = active_reaction;
var previous_component_context = component_context;
const [suspend, unsuspend] = create_suspense();
try {
suspend();
return await promise;
} finally {
set_active_effect(previous_effect);
set_active_reaction(previous_reaction);
set_component_context(previous_component_context);
unsuspend();
}
}

@ -129,7 +129,7 @@ export {
update_store,
mark_store_binding
} from './reactivity/store.js';
export { boundary } from './dom/blocks/boundary.js';
export { boundary, preserve_context } from './dom/blocks/boundary.js';
export { set_text } from './render.js';
export {
get,

@ -419,6 +419,7 @@ declare module 'svelte' {
render: () => string;
setup?: (element: Element) => void | (() => void);
}): Snippet<Params>;
export function create_suspense(): (() => void)[];
/** Anything except a function */
type NotFunction<T> = T extends Function ? never : T;
/**

Loading…
Cancel
Save