Skip to content

Commit de84f6b

Browse files
committed
init
1 parent e883cd0 commit de84f6b

File tree

8 files changed

+166
-18
lines changed

8 files changed

+166
-18
lines changed

.changeset/spicy-hotels-applaud.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: parallelize more async work

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { VariableDeclaration } from './visitors/VariableDeclaration.js';
6363

6464
/** @type {Visitors} */
6565
const visitors = {
66-
_: function set_scope(node, { next, state }) {
66+
_: function set_scope(node, { path, next, state }) {
6767
const scope = state.scopes.get(node);
6868

6969
if (scope && scope !== state.scope) {
@@ -84,6 +84,9 @@ const visitors = {
8484
} else {
8585
next();
8686
}
87+
if (node.type !== 'VariableDeclaration' && path.at(-1)?.type === 'Program' && state.analysis.instance) {
88+
state.current_parallelized_chunk = null;
89+
}
8790
},
8891
AnimateDirective,
8992
ArrowFunctionExpression,
@@ -176,7 +179,9 @@ export function client_component(analysis, options) {
176179
update: /** @type {any} */ (null),
177180
after_update: /** @type {any} */ (null),
178181
template: /** @type {any} */ (null),
179-
memoizer: /** @type {any} */ (null)
182+
memoizer: /** @type {any} */ (null),
183+
parallelized_derived_chunks: [],
184+
current_parallelized_chunk: null
180185
};
181186

182187
const module = /** @type {ESTree.Program} */ (

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
AssignmentExpression,
88
UpdateExpression,
99
VariableDeclaration,
10-
Declaration
10+
Pattern
1111
} from 'estree';
1212
import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
1313
import type { TransformState } from '../types.js';
@@ -83,6 +83,18 @@ export interface ComponentClientTransformState extends ClientTransformState {
8383
readonly instance_level_snippets: VariableDeclaration[];
8484
/** Snippets hoisted to the module */
8585
readonly module_level_snippets: VariableDeclaration[];
86+
readonly parallelized_derived_chunks: ParallelizedChunk[];
87+
current_parallelized_chunk: ParallelizedChunk | null;
88+
}
89+
90+
export interface ParallelizedChunk {
91+
declarators: Array<{
92+
id: Pattern;
93+
init: Expression;
94+
}>;
95+
kind: VariableDeclaration['kind'];
96+
/** index in instance body */
97+
position: number;
8698
}
8799

88100
export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>;

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
/** @import { ArrowFunctionExpression, AssignmentExpression, BlockStatement, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
1+
/** @import { ArrowFunctionExpression, AssignmentExpression, BlockStatement, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
22
/** @import { Binding } from '#compiler' */
33
/** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
4-
/** @import { Analysis } from '../../types.js' */
4+
/** @import { Analysis, ComponentAnalysis } from '../../types.js' */
55
/** @import { Scope } from '../../scope.js' */
66
import * as b from '#compiler/builders';
77
import { is_simple_expression } from '../../../utils/ast.js';
@@ -15,6 +15,7 @@ import {
1515
import { dev } from '../../../state.js';
1616
import { walk } from 'zimmerframe';
1717
import { validate_mutation } from './visitors/shared/utils.js';
18+
import is_reference from 'is-reference';
1819

1920
/**
2021
* @param {Binding} binding
@@ -28,6 +29,60 @@ export function is_state_source(binding, analysis) {
2829
);
2930
}
3031

32+
/**
33+
* @param {Expression} expression
34+
* @param {Scope} scope
35+
* @param {Analysis | ComponentAnalysis} analysis
36+
* @returns {boolean}
37+
*/
38+
export function can_be_parallelized(expression, scope, analysis) {
39+
let has_closures = false;
40+
/** @type {Set<string>} */
41+
const references = new Set();
42+
walk(expression, null, {
43+
ArrowFunctionExpression(_, { stop }) {
44+
has_closures = true;
45+
stop();
46+
},
47+
FunctionExpression(_, { stop }) {
48+
has_closures = true;
49+
stop();
50+
},
51+
Identifier(node, { path }) {
52+
if (is_reference(node, /** @type {Node} */ (path.at(-1)))) {
53+
references.add(node.name);
54+
}
55+
}
56+
});
57+
if (has_closures) {
58+
return false;
59+
}
60+
for (const reference of references) {
61+
const binding = scope.get(reference);
62+
if (!binding || binding.declaration_kind === 'import') {
63+
return false;
64+
}
65+
if ('template' in analysis) {
66+
if (binding.scope !== analysis.instance.scope) {
67+
return false;
68+
}
69+
} else if (binding.scope !== analysis.module.scope) {
70+
return false;
71+
}
72+
73+
if (binding.kind === 'derived') {
74+
const init = /** @type {CallExpression} */ (binding.initial);
75+
if (analysis.async_deriveds.has(init)) {
76+
return false;
77+
}
78+
}
79+
if (!binding.mutated && !binding.reassigned) {
80+
continue;
81+
}
82+
}
83+
return true;
84+
}
85+
3186
/**
3287
* @param {Identifier} node
3388
* @param {ClientTransformState} state

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import * as b from '#compiler/builders';
55
import { add_state_transformers } from './shared/declarations.js';
66

77
/**
8-
* @param {Program} _
8+
* @param {Program} node
99
* @param {ComponentContext} context
1010
*/
11-
export function Program(_, context) {
11+
export function Program(node, context) {
1212
if (!context.state.analysis.runes) {
1313
context.state.transform['$$props'] = {
1414
read: (node) => ({ ...node, name: '$$sanitized_props' })
@@ -137,5 +137,20 @@ export function Program(_, context) {
137137

138138
add_state_transformers(context);
139139

140-
context.next();
140+
/** @type {Program['body']} */
141+
const body = [];
142+
for (let i = 0; i < node.body.length; i++) {
143+
const transformed = /** @type {Program['body'][number]} */ (context.visit(node.body[i]));
144+
const chunk = context.state.parallelized_derived_chunks?.at(-1);
145+
body.push(transformed);
146+
if (chunk && chunk.position === i) {
147+
const pattern = b.array_pattern(chunk.declarators.map(({ id }) => id));
148+
const init = b.call('$.all', b.array(chunk.declarators.map(({ init }) => init)));
149+
body.push(b.declaration(chunk.kind, [b.declarator(pattern, b.await(init))]));
150+
}
151+
}
152+
return {
153+
...node,
154+
body
155+
};
141156
}

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

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
/** @import { CallExpression, Expression, Identifier, Literal, VariableDeclaration, VariableDeclarator } from 'estree' */
1+
/** @import { CallExpression, Expression, Identifier, Literal, Program, VariableDeclaration, VariableDeclarator } from 'estree' */
22
/** @import { Binding } from '#compiler' */
3-
/** @import { ComponentContext } from '../types' */
3+
/** @import { ComponentContext, ParallelizedChunk } from '../types' */
44
import { dev, is_ignored, locate_node } from '../../../../state.js';
55
import { extract_paths } from '../../../../utils/ast.js';
66
import * as b from '#compiler/builders';
77
import * as assert from '../../../../utils/assert.js';
88
import { get_rune } from '../../../scope.js';
9-
import { get_prop_source, is_prop_source, is_state_source, should_proxy } from '../utils.js';
9+
import {
10+
can_be_parallelized,
11+
get_prop_source,
12+
is_prop_source,
13+
is_state_source,
14+
should_proxy
15+
} from '../utils.js';
1016
import { is_hoisted_function } from '../../utils.js';
1117
import { get_value } from './shared/declarations.js';
1218

@@ -200,6 +206,18 @@ export function VariableDeclaration(node, context) {
200206
const is_async = context.state.analysis.async_deriveds.has(
201207
/** @type {CallExpression} */ (init)
202208
);
209+
let parallelize = false;
210+
if (
211+
is_async &&
212+
context.state.analysis.instance &&
213+
context.state.scope === context.state.analysis.instance.scope &&
214+
!dev
215+
) {
216+
parallelize = can_be_parallelized(value, context.state.scope, context.state.analysis);
217+
}
218+
219+
/** @type {VariableDeclarator[]} */
220+
const derived_declarators = [];
203221

204222
if (declarator.id.type === 'Identifier') {
205223
let expression = /** @type {Expression} */ (
@@ -212,22 +230,22 @@ export function VariableDeclaration(node, context) {
212230
if (is_async) {
213231
const location = dev && !is_ignored(init, 'await_waterfall') && locate_node(init);
214232
let call = b.call(
215-
'$.async_derived',
233+
'$.async_derived' + (parallelize ? '_p' : ''),
216234
b.thunk(expression, true),
217235
location ? b.literal(location) : undefined
218236
);
219237

220238
call = b.call(b.await(b.call('$.save', call)));
221239
if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name));
222240

223-
declarations.push(b.declarator(declarator.id, call));
241+
derived_declarators.push(b.declarator(declarator.id, call));
224242
} else {
225243
if (rune === '$derived') expression = b.thunk(expression);
226244

227245
let call = b.call('$.derived', expression);
228246
if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name));
229247

230-
declarations.push(b.declarator(declarator.id, call));
248+
derived_declarators.push(b.declarator(declarator.id, call));
231249
}
232250
} else {
233251
const init = /** @type {CallExpression} */ (declarator.init);
@@ -253,15 +271,17 @@ export function VariableDeclaration(node, context) {
253271
b.thunk(expression, true),
254272
location ? b.literal(location) : undefined
255273
);
256-
call = b.call(b.await(b.call('$.save', call)));
274+
if (!parallelize) {
275+
call = b.call(b.await(b.call('$.save', call)));
276+
}
257277
}
258278

259279
if (dev) {
260280
const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`;
261281
call = b.call('$.tag', call, b.literal(label));
262282
}
263283

264-
declarations.push(b.declarator(id, call));
284+
derived_declarators.push(b.declarator(id, call));
265285
}
266286

267287
const { inserts, paths } = extract_paths(declarator.id, rhs);
@@ -278,13 +298,13 @@ export function VariableDeclaration(node, context) {
278298
call = b.call('$.tag', call, b.literal(label));
279299
}
280300

281-
declarations.push(b.declarator(id, call));
301+
derived_declarators.push(b.declarator(id, call));
282302
}
283303

284304
for (const path of paths) {
285305
const expression = /** @type {Expression} */ (context.visit(path.expression));
286306
const call = b.call('$.derived', b.thunk(expression));
287-
declarations.push(
307+
derived_declarators.push(
288308
b.declarator(
289309
path.node,
290310
dev
@@ -295,6 +315,32 @@ export function VariableDeclaration(node, context) {
295315
}
296316
}
297317

318+
if (!parallelize) {
319+
declarations.push(...derived_declarators);
320+
} else if (derived_declarators.length > 0) {
321+
/** @type {ParallelizedChunk['declarators']} */
322+
const declarators = derived_declarators.map(({ id, init }) => ({
323+
id,
324+
init: /** @type {Expression} */ (init)
325+
}));
326+
if (
327+
context.state.current_parallelized_chunk &&
328+
context.state.current_parallelized_chunk.kind === node.kind &&
329+
context.state.current_parallelized_chunk.position ===
330+
/** @type {Program} */ (context.path.at(-1)).body.indexOf(node)
331+
) {
332+
context.state.current_parallelized_chunk.declarators.push(...declarators);
333+
} else {
334+
const chunk = {
335+
kind: node.kind,
336+
declarators,
337+
position: /** @type {Program} */ (context.path.at(-1)).body.indexOf(node)
338+
};
339+
context.state.current_parallelized_chunk = chunk;
340+
context.state.parallelized_derived_chunks.push(chunk);
341+
}
342+
}
343+
298344
continue;
299345
}
300346
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export {
9999
with_script
100100
} from './dom/template.js';
101101
export {
102+
all,
102103
async_body,
103104
for_await_track_reactivity_loss,
104105
save,

packages/svelte/src/internal/client/reactivity/async.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,12 @@ export async function async_body(fn) {
189189
unsuspend();
190190
}
191191
}
192+
193+
/**
194+
* @template T
195+
* @param {Array<Promise<T>>} promises
196+
* @returns {Promise<Array<T>>}
197+
*/
198+
export function all(...promises) {
199+
return Promise.all(promises.map((promise) => save(promise).then((restore) => restore())));
200+
}

0 commit comments

Comments
 (0)