pull/17038/head
Rich Harris 4 days ago
parent db593ae2ff
commit 249faa5588

@ -555,8 +555,8 @@ export function analyze_component(root, source, options) {
snippets: new Set(),
async_deriveds: new Set(),
pickled_awaits: new Set(),
awaited_declarations: new Map(),
awaited_statements: new Map()
awaited_statements: new Map(),
promise_indexes: new Map()
};
if (!runes) {

@ -22,25 +22,6 @@ export function IfBlock(node, context) {
expression: node.metadata.expression
});
// TODO this can be helperised
for (const binding of node.metadata.expression.dependencies) {
const awaited = context.state.analysis.awaited_declarations.get(binding.node.name);
if (awaited) {
node.metadata.async ??= {
declarations: new Set()
};
node.metadata.async.declarations.add(awaited);
}
}
if (node.metadata.expression.has_await) {
node.metadata.async ??= {
declarations: new Set()
};
}
context.visit(node.consequent);
if (node.alternate) context.visit(node.alternate);
}

@ -25,8 +25,16 @@ export function IfBlock(node, context) {
statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate)));
}
// TODO helperise
const promise_index = Array.from(node.metadata.expression.dependencies).reduce(
(index, binding) => Math.max(index, context.state.analysis.promise_indexes.get(binding) ?? -1),
-1
);
const is_async = promise_index > -1 || node.metadata.expression.has_await;
const expression = build_expression(context, node.test, node.metadata.expression);
const test = node.metadata.async ? b.call('$.get', b.id('$$condition')) : expression;
const test = is_async ? b.call('$.get', b.id('$$condition')) : expression;
/** @type {Expression[]} */
const args = [
@ -70,20 +78,14 @@ export function IfBlock(node, context) {
statements.push(add_svelte_meta(b.call('$.if', ...args), node, 'if'));
if (node.metadata.async) {
if (is_async) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
b.array([...node.metadata.async.declarations].map((d) => d.id)),
b.array([
b.arrow(
[...node.metadata.async.declarations].map((d) => d.pattern),
expression,
node.metadata.expression.has_await
)
]),
promise_index === -1 ? undefined : b.id(`$$promises[${promise_index}]`),
b.array([b.thunk(expression, node.metadata.expression.has_await)]),
b.arrow([context.state.node, b.id('$$condition')], b.block(statements))
)
)

@ -166,7 +166,7 @@ function transform_body(program, context) {
/** @type {AwaitedStatement[]} */
const deriveds = [];
const { awaited_statements } = context.state.analysis;
const { awaited_statements, promise_indexes } = context.state.analysis;
let awaited = false;
@ -188,14 +188,17 @@ function transform_body(program, context) {
return;
}
if (node.type === 'VariableDeclarator') {
const rune = get_rune(node.init, context.state.scope);
// TODO put deriveds into a separate array, and group them immediately
// after their latest dependency. for now, to avoid having to figure
// out the intricacies of dependency tracking, just let 'em waterfall
// if (node.type === 'VariableDeclarator') {
// const rune = get_rune(node.init, context.state.scope);
if (rune === '$derived' || rune === '$derived.by') {
deriveds.push(statement);
return;
}
}
// if (rune === '$derived' || rune === '$derived.by') {
// deriveds.push(statement);
// return;
// }
// }
statements.push(statement);
};
@ -306,5 +309,13 @@ function transform_body(program, context) {
// console.log('statements', statements);
// console.log('deriveds', deriveds);
for (let i = 0; i < statements.length; i += 1) {
const s = statements[i];
for (const binding of s.declarations) {
promise_indexes.set(binding, i);
}
}
return out;
}

@ -129,23 +129,15 @@ export interface ComponentAnalysis extends Analysis {
* Every snippet that is declared locally
*/
snippets: Set<AST.SnippetBlock>;
/**
* A lookup of awaited declarations. If you have something this in `<script>`...
*
* let a = await get_a();
* let b = get_b();
*
* ...it will get transformed to something like this...
*
* let $$0 = $.run([], () => get_a());
* let $$1 = $.run([$$0], () => get_b());
*
* ...and references to `a` or `b` in the template should be mediated by `$$0` and `$$1`
*/
awaited_declarations: Map<string, AwaitedDeclaration>;
/**
* Information about top-level instance statements that need to be transformed
* so that we can run the template synchronously
*/
awaited_statements: Map<Statement | ModuleDeclaration | VariableDeclarator, AwaitedStatement>;
/**
* A map that tells us which of the `$$promises` needs to be awaited
* before a particular binding can be accessed
* TODO this gets populated during transform, which feels wrong
*/
promise_indexes: Map<Binding, number>;
}

@ -466,9 +466,6 @@ export namespace AST {
alternate: Fragment | null;
/** @internal */
metadata: {
async: {
declarations: Set<AwaitedDeclaration>;
};
expression: ExpressionMetadata;
};
}

@ -14,11 +14,11 @@ import { get_boundary } from './boundary.js';
/**
* @param {TemplateNode} node
* @param {Array<Promise<any>>} dependencies
* @param {Promise<void> | undefined} blocker
* @param {Array<() => Promise<any>>} expressions
* @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
*/
export function async(node, dependencies, expressions, fn) {
export function async(node, blocker, expressions, fn) {
var boundary = get_boundary();
var batch = /** @type {Batch} */ (current_batch);
var blocking = !boundary.is_pending();
@ -36,7 +36,7 @@ export function async(node, dependencies, expressions, fn) {
set_hydrate_node(end);
}
flatten(dependencies, [], expressions, (values) => {
flatten(blocker, [], expressions, (values) => {
if (was_hydrating) {
set_hydrating(true);
set_hydrate_node(previous_hydrate_node);

@ -34,14 +34,15 @@ import {
skip_nodes
} from '../dom/hydration.js';
import { create_text } from '../dom/operations.js';
import { noop } from '../../shared/utils.js';
/**
* @param {Array<Promise<any>>} dependencies
* @param {Promise<void> | undefined} blocker
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {(values: Value[]) => any} fn
*/
export function flatten(dependencies, sync, async, fn) {
export function flatten(blocker, sync, async, fn) {
const d = is_runes() ? derived : derived_safe_equal;
if (async.length === 0) {
@ -56,12 +57,10 @@ export function flatten(dependencies, sync, async, fn) {
var was_hydrating = hydrating;
Promise.all(dependencies).then((values) => {
Promise.resolve(blocker).then((values) => {
restore();
const result = Promise.all(
async.map((expression) => async_derived(() => expression(...values)))
)
const result = Promise.all(async.map((expression) => async_derived(expression)))
.then((result) => {
restore();
@ -271,10 +270,25 @@ export async function async_body(anchor, fn) {
}
/**
* @param {Array<Promise<any>>} deps
* @param {(...deps: any) => any} fn
* @param {Array<() => void | Promise<void>>} thunks
*/
export function run(deps, fn) {
// TODO save/restore context
return Promise.all(deps).then(fn);
export function run(thunks) {
const restore = capture();
let promise = Promise.resolve();
return thunks.map((fn) => {
promise = promise
.then(() => {
try {
restore();
return fn();
} finally {
unset_context();
}
})
.then(noop);
return promise;
});
}

@ -365,9 +365,10 @@ export function render_effect(fn, flags = 0) {
* @param {(...expressions: any) => void | (() => void)} fn
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {Promise<void>} [blocker]
*/
export function template_effect(fn, sync = [], async = []) {
flatten(sync, async, (values) => {
export function template_effect(fn, sync = [], async = [], blocker) {
flatten(blocker, sync, async, (values) => {
create_effect(RENDER_EFFECT, () => fn(...values.map(get)), true);
});
}

Loading…
Cancel
Save