Skip to content

Commit 0d8f27e

Browse files
committed
parallelize
1 parent 1a72d28 commit 0d8f27e

File tree

12 files changed

+87
-91
lines changed

12 files changed

+87
-91
lines changed

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ export function client_component(analysis, options) {
160160
},
161161
namespace: options.namespace,
162162
bound_contenteditable: false,
163-
init_is_async: false,
164-
update_is_async: false
163+
async: []
165164
},
166165
events: new Set(),
167166
preserve_whitespace: options.preserveWhitespace,

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ export interface ComponentClientTransformState extends ClientTransformState {
7575
*/
7676
template_contains_script_tag: boolean;
7777
};
78-
// TODO it would be nice if these were colocated with the arrays they pertain to
79-
init_is_async: boolean;
80-
update_is_async: boolean;
78+
/**
79+
* Synthetic async deriveds belonging to the current fragment
80+
*/
81+
async: Array<{ id: Identifier; expression: Expression }>;
8182
};
8283
readonly preserve_whitespace: boolean;
8384

packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js

+33-5
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ export function Fragment(node, context) {
7575
},
7676
namespace,
7777
bound_contenteditable: context.state.metadata.bound_contenteditable,
78-
init_is_async: false,
79-
update_is_async: false
78+
async: []
8079
}
8180
};
8281

@@ -192,7 +191,7 @@ export function Fragment(node, context) {
192191
}
193192

194193
if (state.update.length > 0) {
195-
body.push(build_render_statement(state.update, state.metadata.update_is_async));
194+
body.push(build_render_statement(state.update));
196195
}
197196

198197
body.push(...state.after_update);
@@ -205,12 +204,41 @@ export function Fragment(node, context) {
205204
}
206205

207206
const async =
208-
state.metadata.init_is_async || (state.analysis.is_async && context.path.length === 0);
207+
state.metadata.async.length > 0 || (state.analysis.is_async && context.path.length === 0);
209208

210209
if (async) {
211210
// TODO need to create bookends for hydration to work
212211
return b.block([
213-
b.function_declaration(b.id('$$body'), [b.id('$$anchor')], b.block(body), true),
212+
b.function_declaration(
213+
b.id('$$body'),
214+
[b.id('$$anchor')],
215+
b.block([
216+
b.var(
217+
b.array_pattern(state.metadata.async.map(({ id }) => id)),
218+
b.call(
219+
b.member(
220+
b.await(
221+
b.call(
222+
'$.suspend',
223+
b.call(
224+
'Promise.all',
225+
b.array(
226+
state.metadata.async.map(({ expression }) =>
227+
b.call('$.async_derived', b.thunk(expression, true))
228+
)
229+
)
230+
)
231+
)
232+
),
233+
'exit'
234+
)
235+
)
236+
),
237+
...body,
238+
b.stmt(b.call('$.exit'))
239+
]),
240+
true
241+
),
214242

215243
b.var('fragment', b.call('$.comment')),
216244
b.var('node', b.call('$.first_child', b.id('fragment'))),

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,7 @@ export function RegularElement(node, context) {
409409
b.block([
410410
...child_state.init,
411411
...element_state.init,
412-
child_state.update.length > 0
413-
? build_render_statement(child_state.update, child_state.metadata.update_is_async)
414-
: b.empty,
412+
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
415413
...child_state.after_update,
416414
...element_state.after_update
417415
])
@@ -420,9 +418,6 @@ export function RegularElement(node, context) {
420418
context.state.init.push(...child_state.init, ...element_state.init);
421419
context.state.update.push(...child_state.update);
422420
context.state.after_update.push(...child_state.after_update, ...element_state.after_update);
423-
424-
context.state.metadata.init_is_async ||= child_state.metadata.init_is_async;
425-
context.state.metadata.update_is_async ||= child_state.metadata.update_is_async;
426421
} else {
427422
context.state.init.push(...element_state.init);
428423
context.state.after_update.push(...element_state.after_update);
@@ -632,10 +627,9 @@ function build_element_attribute_update_assignment(
632627

633628
if (attribute.metadata.expression.has_state) {
634629
if (has_call) {
635-
state.init.push(build_update(update, attribute.metadata.expression.is_async));
630+
state.init.push(build_update(update));
636631
} else {
637632
state.update.push(update);
638-
state.metadata.update_is_async ||= attribute.metadata.expression.is_async;
639633
}
640634
return true;
641635
} else {
@@ -668,10 +662,9 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
668662

669663
if (attribute.metadata.expression.has_state) {
670664
if (has_call) {
671-
state.init.push(build_update(update, attribute.metadata.expression.is_async));
665+
state.init.push(build_update(update));
672666
} else {
673667
state.update.push(update);
674-
state.metadata.update_is_async ||= attribute.metadata.expression.is_async;
675668
}
676669
return true;
677670
} else {

packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,7 @@ export function SvelteElement(node, context) {
123123
/** @type {Statement[]} */
124124
const inner = inner_context.state.init;
125125
if (inner_context.state.update.length > 0) {
126-
inner.push(
127-
build_render_statement(
128-
inner_context.state.update,
129-
inner_context.state.metadata.update_is_async
130-
)
131-
);
126+
inner.push(build_render_statement(inner_context.state.update));
132127
}
133128
inner.push(...inner_context.state.after_update);
134129
inner.push(

packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { build_template_chunk } from './shared/utils.js';
88
* @param {ComponentContext} context
99
*/
1010
export function TitleElement(node, context) {
11-
const { has_state, is_async, value } = build_template_chunk(
11+
const { has_state, value } = build_template_chunk(
1212
/** @type {any} */ (node.fragment.nodes),
1313
context.visit,
1414
context.state
@@ -18,12 +18,7 @@ export function TitleElement(node, context) {
1818

1919
if (has_state) {
2020
context.state.update.push(statement);
21-
context.state.metadata.update_is_async ||= is_async;
2221
} else {
23-
if (is_async) {
24-
throw new Error('TODO top-level await');
25-
}
26-
2722
context.state.init.push(statement);
2823
}
2924
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,6 @@ export function build_component(node, component_name, context, anchor = context.
9494
}
9595

9696
for (const attribute of node.attributes) {
97-
if (attribute.type === 'Attribute' || attribute.type === 'SpreadAttribute') {
98-
context.state.metadata.init_is_async ||= attribute.metadata.expression.is_async;
99-
}
100-
10197
if (attribute.type === 'LetDirective') {
10298
if (!slot_scope_applies_to_itself) {
10399
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute, states.default)));
@@ -169,10 +165,11 @@ export function build_component(node, component_name, context, anchor = context.
169165
const id = b.id(context.state.scope.generate(attribute.name));
170166

171167
if (attribute.metadata.expression.is_async) {
172-
// TODO parallelise these
173-
context.state.init.push(
174-
b.var(id, b.await(b.call('$.async_derived', b.thunk(arg, true))))
175-
);
168+
context.state.metadata.async.push({
169+
id,
170+
expression: arg
171+
});
172+
176173
arg = b.call(id);
177174
} else {
178175
context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js

+8-13
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ export function build_set_attributes(
8383
context.state.init.push(b.let(attributes_id));
8484
const update = b.stmt(b.assignment('=', attributes_id, call));
8585
context.state.update.push(update);
86-
context.state.metadata.update_is_async ||= is_async;
8786
return true;
8887
}
8988

@@ -115,7 +114,9 @@ export function build_style_directives(
115114
? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
116115
: build_attribute_value(directive.value, context).value;
117116

118-
if (has_call) {
117+
if (is_async) {
118+
throw new Error('TODO');
119+
} else if (has_call) {
119120
const id = b.id(state.scope.generate('style_directive'));
120121

121122
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
@@ -133,14 +134,10 @@ export function build_style_directives(
133134
);
134135

135136
if (!is_attributes_reactive && has_call) {
136-
state.init.push(build_update(update, is_async));
137+
state.init.push(build_update(update));
137138
} else if (is_attributes_reactive || has_state || has_call) {
138139
state.update.push(update);
139-
state.metadata.update_is_async ||= is_async;
140140
} else {
141-
if (is_async) {
142-
throw new Error('TODO top-level await');
143-
}
144141
state.init.push(update);
145142
}
146143
}
@@ -165,7 +162,9 @@ export function build_class_directives(
165162
const { has_state, has_call, is_async } = directive.metadata.expression;
166163
let value = /** @type {Expression} */ (context.visit(directive.expression));
167164

168-
if (has_call) {
165+
if (is_async) {
166+
throw new Error('TODO');
167+
} else if (has_call) {
169168
const id = b.id(state.scope.generate('class_directive'));
170169

171170
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
@@ -175,14 +174,10 @@ export function build_class_directives(
175174
const update = b.stmt(b.call('$.toggle_class', element_id, b.literal(directive.name), value));
176175

177176
if (!is_attributes_reactive && has_call) {
178-
state.init.push(build_update(update, is_async));
177+
state.init.push(build_update(update));
179178
} else if (is_attributes_reactive || has_state || has_call) {
180179
state.update.push(update);
181-
state.metadata.update_is_async ||= is_async;
182180
} else {
183-
if (is_async) {
184-
throw new Error('TODO top-level await');
185-
}
186181
state.init.push(update);
187182
}
188183
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
6969

7070
state.template.push(' ');
7171

72-
const { has_state, has_call, is_async, value } = build_template_chunk(sequence, visit, state);
72+
const { has_state, has_call, value } = build_template_chunk(sequence, visit, state);
7373

7474
// if this is a standalone `{expression}`, make sure we handle the case where
7575
// 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 }) {
7979
const update = b.stmt(b.call('$.set_text', id, value));
8080

8181
if (has_call && !within_bound_contenteditable) {
82-
state.init.push(build_update(update, is_async));
82+
state.init.push(build_update(update));
8383
} else if (has_state && !within_bound_contenteditable) {
8484
state.update.push(update);
85-
state.metadata.update_is_async ||= is_async;
8685
} else {
87-
if (is_async) {
88-
throw new Error('TODO top-level await');
89-
}
9086
state.init.push(b.stmt(b.assignment('=', b.member(id, 'nodeValue'), value)));
9187
}
9288
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

+22-31
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { locator } from '../../../../../state.js';
1414
* @param {Array<AST.Text | AST.ExpressionTag>} values
1515
* @param {(node: AST.SvelteNode, state: any) => any} visit
1616
* @param {ComponentClientTransformState} state
17-
* @returns {{ value: Expression, has_state: boolean, has_call: boolean, is_async: boolean }}
17+
* @returns {{ value: Expression, has_state: boolean, has_call: boolean }}
1818
*/
1919
export function build_template_chunk(values, visit, state) {
2020
/** @type {Expression[]} */
@@ -26,16 +26,15 @@ export function build_template_chunk(values, visit, state) {
2626
let has_call = false;
2727
let has_state = false;
2828
let is_async = false;
29-
let contains_multiple_call_expression = false;
29+
let should_memoize = false;
3030

3131
for (const node of values) {
3232
if (node.type === 'ExpressionTag') {
3333
const metadata = node.metadata.expression;
3434

35-
contains_multiple_call_expression ||= has_call && metadata.has_call;
35+
should_memoize ||= (has_call || is_async) && (metadata.has_call || metadata.is_async);
3636
has_call ||= metadata.has_call;
3737
has_state ||= metadata.has_state;
38-
is_async ||= metadata.is_async;
3938
}
4039
}
4140

@@ -49,32 +48,26 @@ export function build_template_chunk(values, visit, state) {
4948
quasi.value.cooked += node.expression.value + '';
5049
}
5150
} else {
52-
if (contains_multiple_call_expression) {
53-
const id = b.id(state.scope.generate('stringified_text'));
51+
const expression = /** @type {Expression} */ (visit(node.expression, state));
52+
53+
if (node.metadata.expression.is_async) {
54+
const id = b.id(state.scope.generate('expression'));
55+
state.metadata.async.push({ id, expression: b.logical('??', expression, b.literal('')) });
56+
57+
expressions.push(b.call(id));
58+
} else if (node.metadata.expression.has_call && should_memoize) {
59+
const id = b.id(state.scope.generate('expression'));
5460
state.init.push(
55-
b.const(
56-
id,
57-
create_derived(
58-
state,
59-
b.thunk(
60-
b.logical(
61-
'??',
62-
/** @type {Expression} */ (visit(node.expression, state)),
63-
b.literal('')
64-
),
65-
is_async
66-
)
67-
)
68-
)
61+
b.const(id, create_derived(state, b.thunk(b.logical('??', expression, b.literal('')))))
6962
);
7063

71-
expressions.push(is_async ? b.await(b.call('$.get', id)) : b.call('$.get', id));
64+
expressions.push(b.call('$.get', id));
7265
} else if (values.length === 1) {
7366
// If we have a single expression, then pass that in directly to possibly avoid doing
7467
// extra work in the template_effect (instead we do the work in set_text).
75-
return { value: visit(node.expression, state), has_state, has_call, is_async };
68+
return { value: expression, has_state, has_call };
7669
} else {
77-
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
70+
expressions.push(b.logical('??', expression, b.literal('')));
7871
}
7972

8073
quasi = b.quasi('', i + 1 === values.length);
@@ -88,28 +81,26 @@ export function build_template_chunk(values, visit, state) {
8881

8982
const value = b.template(quasis, expressions);
9083

91-
return { value, has_state, has_call, is_async };
84+
return { value, has_state, has_call };
9285
}
9386

9487
/**
9588
* @param {Statement} statement
96-
* @param {boolean} is_async
9789
*/
98-
export function build_update(statement, is_async) {
90+
export function build_update(statement) {
9991
const body =
10092
statement.type === 'ExpressionStatement' ? statement.expression : b.block([statement]);
10193

102-
return b.stmt(b.call('$.template_effect', b.thunk(body, is_async)));
94+
return b.stmt(b.call('$.template_effect', b.thunk(body)));
10395
}
10496

10597
/**
10698
* @param {Statement[]} update
107-
* @param {boolean} is_async
10899
*/
109-
export function build_render_statement(update, is_async) {
100+
export function build_render_statement(update) {
110101
return update.length === 1
111-
? build_update(update[0], is_async)
112-
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update), is_async)));
102+
? build_update(update[0])
103+
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update))));
113104
}
114105

115106
/**

0 commit comments

Comments
 (0)