async props

aaa
Rich Harris 8 months ago
parent 5ae974f47d
commit c34e44f781

@ -1,13 +1,19 @@
/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */
/** @import { ComponentContext, MemoizedExpression } from '../../types.js' */
import { dev, is_ignored } from '../../../../../state.js';
import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
import {
build_bind_this,
get_expression_id,
memoize_expression,
validate_binding
} from '../shared/utils.js';
import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js';
import { determine_slot } from '../../../../../utils/slot.js';
import { create_derived } from '../../utils.js';
/**
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
@ -40,6 +46,12 @@ export function build_component(node, component_name, context, anchor = context.
/** @type {Record<string, Expression[]>} */
const events = {};
/** @type {MemoizedExpression[]} */
const expressions = [];
/** @type {MemoizedExpression[]} */
const async_expressions = [];
/** @type {Property[]} */
const custom_css_props = [];
@ -115,16 +127,21 @@ export function build_component(node, component_name, context, anchor = context.
(events[attribute.name] ||= []).push(handler);
} else if (attribute.type === 'SpreadAttribute') {
const expression = /** @type {Expression} */ (context.visit(attribute));
if (attribute.metadata.expression.has_state) {
let value = expression;
if (attribute.metadata.expression.has_call) {
const id = b.id(context.state.scope.generate('spread_element'));
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
value = b.call('$.get', id);
}
props_and_spreads.push(b.thunk(value));
if (attribute.metadata.expression.has_state) {
props_and_spreads.push(
b.thunk(
attribute.metadata.expression.is_async || attribute.metadata.expression.has_call
? b.call(
'$.get',
get_expression_id(
attribute.metadata.expression.is_async ? async_expressions : expressions,
expression
)
)
: expression
)
);
} else {
props_and_spreads.push(expression);
}
@ -133,10 +150,15 @@ export function build_component(node, component_name, context, anchor = context.
custom_css_props.push(
b.init(
attribute.name,
build_attribute_value(attribute.value, context, (value, metadata) =>
build_attribute_value(attribute.value, context, (value, metadata) => {
// TODO put the derived in the local block
metadata.has_call ? memoize_expression(context.state, value) : value
).value
return metadata.has_call || metadata.is_async
? b.call(
'$.get',
get_expression_id(metadata.is_async ? async_expressions : expressions, value)
)
: value;
}).value
)
);
continue;
@ -154,7 +176,7 @@ export function build_component(node, component_name, context, anchor = context.
attribute.value,
context,
(value, metadata) => {
if (!metadata.has_state) return value;
if (!metadata.has_state && !metadata.is_async) return value;
// When we have a non-simple computation, anything other than an Identifier or Member expression,
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
@ -167,7 +189,12 @@ export function build_component(node, component_name, context, anchor = context.
);
});
return should_wrap_in_derived ? memoize_expression(context.state, value) : value;
return should_wrap_in_derived
? b.call(
'$.get',
get_expression_id(metadata.is_async ? async_expressions : expressions, value)
)
: value;
}
);
@ -420,7 +447,12 @@ export function build_component(node, component_name, context, anchor = context.
};
}
const statements = [...snippet_declarations];
const statements = [
...snippet_declarations,
...expressions.map((memo) =>
b.let(memo.id, create_derived(context.state, b.thunk(memo.expression)))
)
];
if (node.type === 'SvelteComponent') {
const prev = fn;
@ -457,5 +489,20 @@ export function build_component(node, component_name, context, anchor = context.
statements.push(b.stmt(fn(anchor)));
}
[...async_expressions, ...expressions].forEach((memo, i) => {
memo.id.name = `$${i}`;
});
if (async_expressions.length > 0) {
return b.stmt(
b.call(
'$.async',
anchor,
b.array(async_expressions.map(({ expression }) => b.thunk(expression, true))),
b.arrow([b.id('$$anchor'), ...async_expressions.map(({ id }) => id)], b.block(statements))
)
);
}
return statements.length > 1 ? b.block(statements) : statements[0];
}

@ -0,0 +1,17 @@
/** @import { TemplateNode, Value } from '#client' */
import { async_derived } from '../../reactivity/deriveds.js';
import { suspend } from './boundary.js';
/**
* @param {TemplateNode} node
* @param {Array<() => Promise<any>>} expressions
* @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
*/
export function async(node, expressions, fn) {
// TODO handle hydration
suspend(Promise.all(expressions.map(async_derived))).then((result) => {
fn(node, ...result.exit());
});
}

@ -14,6 +14,7 @@ export {
export { check_target, legacy_api } from './dev/legacy.js';
export { trace } from './dev/tracing.js';
export { inspect } from './dev/inspect.js';
export { async } from './dom/blocks/async.js';
export { await_block as await } from './dom/blocks/await.js';
export { if_block as if } from './dom/blocks/if.js';
export { key_block as key } from './dom/blocks/key.js';

@ -1,5 +1,5 @@
<script>
let { num } = $props();
let { value } = $props();
</script>
<p>{num}</p>
<h1>{value}</h1>

@ -22,7 +22,7 @@ export default test({
await Promise.resolve();
await Promise.resolve();
await tick();
assert.htmlEqual(target.innerHTML, '<p>hello</p>');
assert.htmlEqual(target.innerHTML, '<h1>hello</h1>');
d = deferred();
component.promise = d.promise;
@ -32,6 +32,6 @@ export default test({
d.resolve('hello again');
await Promise.resolve();
await tick();
assert.htmlEqual(target.innerHTML, '<p>hello again</p>');
assert.htmlEqual(target.innerHTML, '<h1>hello again</h1>');
}
});

Loading…
Cancel
Save