parallelize

aaa
Rich Harris 8 months ago
parent 1a72d285f6
commit 0d8f27eae6

@ -160,8 +160,7 @@ export function client_component(analysis, options) {
},
namespace: options.namespace,
bound_contenteditable: false,
init_is_async: false,
update_is_async: false
async: []
},
events: new Set(),
preserve_whitespace: options.preserveWhitespace,

@ -75,9 +75,10 @@ export interface ComponentClientTransformState extends ClientTransformState {
*/
template_contains_script_tag: boolean;
};
// TODO it would be nice if these were colocated with the arrays they pertain to
init_is_async: boolean;
update_is_async: boolean;
/**
* Synthetic async deriveds belonging to the current fragment
*/
async: Array<{ id: Identifier; expression: Expression }>;
};
readonly preserve_whitespace: boolean;

@ -75,8 +75,7 @@ export function Fragment(node, context) {
},
namespace,
bound_contenteditable: context.state.metadata.bound_contenteditable,
init_is_async: false,
update_is_async: false
async: []
}
};
@ -192,7 +191,7 @@ export function Fragment(node, context) {
}
if (state.update.length > 0) {
body.push(build_render_statement(state.update, state.metadata.update_is_async));
body.push(build_render_statement(state.update));
}
body.push(...state.after_update);
@ -205,12 +204,41 @@ export function Fragment(node, context) {
}
const async =
state.metadata.init_is_async || (state.analysis.is_async && context.path.length === 0);
state.metadata.async.length > 0 || (state.analysis.is_async && context.path.length === 0);
if (async) {
// TODO need to create bookends for hydration to work
return b.block([
b.function_declaration(b.id('$$body'), [b.id('$$anchor')], b.block(body), true),
b.function_declaration(
b.id('$$body'),
[b.id('$$anchor')],
b.block([
b.var(
b.array_pattern(state.metadata.async.map(({ id }) => id)),
b.call(
b.member(
b.await(
b.call(
'$.suspend',
b.call(
'Promise.all',
b.array(
state.metadata.async.map(({ expression }) =>
b.call('$.async_derived', b.thunk(expression, true))
)
)
)
)
),
'exit'
)
)
),
...body,
b.stmt(b.call('$.exit'))
]),
true
),
b.var('fragment', b.call('$.comment')),
b.var('node', b.call('$.first_child', b.id('fragment'))),

@ -409,9 +409,7 @@ export function RegularElement(node, context) {
b.block([
...child_state.init,
...element_state.init,
child_state.update.length > 0
? build_render_statement(child_state.update, child_state.metadata.update_is_async)
: b.empty,
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
...child_state.after_update,
...element_state.after_update
])
@ -420,9 +418,6 @@ export function RegularElement(node, context) {
context.state.init.push(...child_state.init, ...element_state.init);
context.state.update.push(...child_state.update);
context.state.after_update.push(...child_state.after_update, ...element_state.after_update);
context.state.metadata.init_is_async ||= child_state.metadata.init_is_async;
context.state.metadata.update_is_async ||= child_state.metadata.update_is_async;
} else {
context.state.init.push(...element_state.init);
context.state.after_update.push(...element_state.after_update);
@ -632,10 +627,9 @@ function build_element_attribute_update_assignment(
if (attribute.metadata.expression.has_state) {
if (has_call) {
state.init.push(build_update(update, attribute.metadata.expression.is_async));
state.init.push(build_update(update));
} else {
state.update.push(update);
state.metadata.update_is_async ||= attribute.metadata.expression.is_async;
}
return true;
} else {
@ -668,10 +662,9 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
if (attribute.metadata.expression.has_state) {
if (has_call) {
state.init.push(build_update(update, attribute.metadata.expression.is_async));
state.init.push(build_update(update));
} else {
state.update.push(update);
state.metadata.update_is_async ||= attribute.metadata.expression.is_async;
}
return true;
} else {

@ -123,12 +123,7 @@ export function SvelteElement(node, context) {
/** @type {Statement[]} */
const inner = inner_context.state.init;
if (inner_context.state.update.length > 0) {
inner.push(
build_render_statement(
inner_context.state.update,
inner_context.state.metadata.update_is_async
)
);
inner.push(build_render_statement(inner_context.state.update));
}
inner.push(...inner_context.state.after_update);
inner.push(

@ -8,7 +8,7 @@ import { build_template_chunk } from './shared/utils.js';
* @param {ComponentContext} context
*/
export function TitleElement(node, context) {
const { has_state, is_async, value } = build_template_chunk(
const { has_state, value } = build_template_chunk(
/** @type {any} */ (node.fragment.nodes),
context.visit,
context.state
@ -18,12 +18,7 @@ export function TitleElement(node, context) {
if (has_state) {
context.state.update.push(statement);
context.state.metadata.update_is_async ||= is_async;
} else {
if (is_async) {
throw new Error('TODO top-level await');
}
context.state.init.push(statement);
}
}

@ -94,10 +94,6 @@ export function build_component(node, component_name, context, anchor = context.
}
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute' || attribute.type === 'SpreadAttribute') {
context.state.metadata.init_is_async ||= attribute.metadata.expression.is_async;
}
if (attribute.type === 'LetDirective') {
if (!slot_scope_applies_to_itself) {
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute, states.default)));
@ -169,10 +165,11 @@ export function build_component(node, component_name, context, anchor = context.
const id = b.id(context.state.scope.generate(attribute.name));
if (attribute.metadata.expression.is_async) {
// TODO parallelise these
context.state.init.push(
b.var(id, b.await(b.call('$.async_derived', b.thunk(arg, true))))
);
context.state.metadata.async.push({
id,
expression: arg
});
arg = b.call(id);
} else {
context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));

@ -83,7 +83,6 @@ export function build_set_attributes(
context.state.init.push(b.let(attributes_id));
const update = b.stmt(b.assignment('=', attributes_id, call));
context.state.update.push(update);
context.state.metadata.update_is_async ||= is_async;
return true;
}
@ -115,7 +114,9 @@ export function build_style_directives(
? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
: build_attribute_value(directive.value, context).value;
if (has_call) {
if (is_async) {
throw new Error('TODO');
} else if (has_call) {
const id = b.id(state.scope.generate('style_directive'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
@ -133,14 +134,10 @@ export function build_style_directives(
);
if (!is_attributes_reactive && has_call) {
state.init.push(build_update(update, is_async));
state.init.push(build_update(update));
} else if (is_attributes_reactive || has_state || has_call) {
state.update.push(update);
state.metadata.update_is_async ||= is_async;
} else {
if (is_async) {
throw new Error('TODO top-level await');
}
state.init.push(update);
}
}
@ -165,7 +162,9 @@ export function build_class_directives(
const { has_state, has_call, is_async } = directive.metadata.expression;
let value = /** @type {Expression} */ (context.visit(directive.expression));
if (has_call) {
if (is_async) {
throw new Error('TODO');
} else if (has_call) {
const id = b.id(state.scope.generate('class_directive'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
@ -175,14 +174,10 @@ export function build_class_directives(
const update = b.stmt(b.call('$.toggle_class', element_id, b.literal(directive.name), value));
if (!is_attributes_reactive && has_call) {
state.init.push(build_update(update, is_async));
state.init.push(build_update(update));
} else if (is_attributes_reactive || has_state || has_call) {
state.update.push(update);
state.metadata.update_is_async ||= is_async;
} else {
if (is_async) {
throw new Error('TODO top-level await');
}
state.init.push(update);
}
}

@ -69,7 +69,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
state.template.push(' ');
const { has_state, has_call, is_async, value } = build_template_chunk(sequence, visit, state);
const { has_state, has_call, 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,14 +79,10 @@ 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, is_async));
state.init.push(build_update(update));
} else if (has_state && !within_bound_contenteditable) {
state.update.push(update);
state.metadata.update_is_async ||= is_async;
} else {
if (is_async) {
throw new Error('TODO top-level await');
}
state.init.push(b.stmt(b.assignment('=', b.member(id, 'nodeValue'), value)));
}
}

@ -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, is_async: boolean }}
* @returns {{ value: Expression, has_state: boolean, has_call: boolean }}
*/
export function build_template_chunk(values, visit, state) {
/** @type {Expression[]} */
@ -26,16 +26,15 @@ 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;
let should_memoize = false;
for (const node of values) {
if (node.type === 'ExpressionTag') {
const metadata = node.metadata.expression;
contains_multiple_call_expression ||= has_call && metadata.has_call;
should_memoize ||= (has_call || is_async) && (metadata.has_call || metadata.is_async);
has_call ||= metadata.has_call;
has_state ||= metadata.has_state;
is_async ||= metadata.is_async;
}
}
@ -49,32 +48,26 @@ export function build_template_chunk(values, visit, state) {
quasi.value.cooked += node.expression.value + '';
}
} else {
if (contains_multiple_call_expression) {
const id = b.id(state.scope.generate('stringified_text'));
const expression = /** @type {Expression} */ (visit(node.expression, state));
if (node.metadata.expression.is_async) {
const id = b.id(state.scope.generate('expression'));
state.metadata.async.push({ id, expression: b.logical('??', expression, b.literal('')) });
expressions.push(b.call(id));
} else if (node.metadata.expression.has_call && should_memoize) {
const id = b.id(state.scope.generate('expression'));
state.init.push(
b.const(
id,
create_derived(
state,
b.thunk(
b.logical(
'??',
/** @type {Expression} */ (visit(node.expression, state)),
b.literal('')
),
is_async
)
)
)
b.const(id, create_derived(state, b.thunk(b.logical('??', expression, b.literal('')))))
);
expressions.push(is_async ? b.await(b.call('$.get', id)) : b.call('$.get', id));
expressions.push(b.call('$.get', id));
} 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, is_async };
return { value: expression, has_state, has_call };
} else {
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
expressions.push(b.logical('??', expression, b.literal('')));
}
quasi = b.quasi('', i + 1 === values.length);
@ -88,28 +81,26 @@ export function build_template_chunk(values, visit, state) {
const value = b.template(quasis, expressions);
return { value, has_state, has_call, is_async };
return { value, has_state, has_call };
}
/**
* @param {Statement} statement
* @param {boolean} is_async
*/
export function build_update(statement, is_async) {
export function build_update(statement) {
const body =
statement.type === 'ExpressionStatement' ? statement.expression : b.block([statement]);
return b.stmt(b.call('$.template_effect', b.thunk(body, is_async)));
return b.stmt(b.call('$.template_effect', b.thunk(body)));
}
/**
* @param {Statement[]} update
* @param {boolean} is_async
*/
export function build_render_statement(update, is_async) {
export function build_render_statement(update) {
return update.length === 1
? build_update(update[0], is_async)
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update), is_async)));
? build_update(update[0])
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update))));
}
/**

@ -285,3 +285,9 @@ export async function suspend(promise) {
}
};
}
export function exit() {
set_active_effect(null);
set_active_reaction(null);
set_component_context(null);
}

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

Loading…
Cancel
Save