Skip to content

Commit

Permalink
async props
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Jan 22, 2025
1 parent 5ae974f commit c34e44f
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 = [];

Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
}
);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
}
17 changes: 17 additions & 0 deletions packages/svelte/src/internal/client/dom/blocks/async.js
Original file line number Diff line number Diff line change
@@ -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());
});
}
1 change: 1 addition & 0 deletions packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
let { num } = $props();
let { value } = $props();
</script>

<p>{num}</p>
<h1>{value}</h1>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>');
}
});

0 comments on commit c34e44f

Please sign in to comment.