diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 62396c60ed..311de0901a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -184,7 +184,7 @@ export function client_component(analysis, options) { after_update: /** @type {any} */ (null), template: /** @type {any} */ (null), memoizer: /** @type {any} */ (null), - parallelized_derived_chunks: [], + parallelized_chunks: [], current_parallelized_chunk: null }; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 4e95ee05b9..9a569f3a9f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -9,7 +9,7 @@ import type { VariableDeclaration, Pattern } from 'estree'; -import type { AST, Namespace, ValidatedCompileOptions } from '#compiler'; +import type { AST, Binding, Namespace, ValidatedCompileOptions } from '#compiler'; import type { TransformState } from '../types.js'; import type { ComponentAnalysis } from '../../types.js'; import type { Template } from './transform-template/template.js'; @@ -83,7 +83,8 @@ export interface ComponentClientTransformState extends ClientTransformState { readonly instance_level_snippets: VariableDeclaration[]; /** Snippets hoisted to the module */ readonly module_level_snippets: VariableDeclaration[]; - readonly parallelized_derived_chunks: ParallelizedChunk[]; + /** async deriveds and certain awaited variables are chunked so they can be parallelized via `Promise.all` */ + readonly parallelized_chunks: ParallelizedChunk[]; current_parallelized_chunk: ParallelizedChunk | null; } @@ -95,6 +96,7 @@ export interface ParallelizedChunk { kind: VariableDeclaration['kind']; /** index in instance body */ position: number; + bindings: Binding[]; } export type Context = import('zimmerframe').Context; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 45bd55f1f4..5a372e17bc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -33,9 +33,10 @@ export function is_state_source(binding, analysis) { * @param {Expression} expression * @param {Scope} scope * @param {Analysis | ComponentAnalysis} analysis + * @param {Binding[]} bindings bindings currently being parallelized (and cannot be accessed) * @returns {boolean} */ -export function can_be_parallelized(expression, scope, analysis) { +export function can_be_parallelized(expression, scope, analysis, bindings) { let has_closures = false; /** @type {Set} */ const references = new Set(); @@ -70,6 +71,10 @@ export function can_be_parallelized(expression, scope, analysis) { return false; } + if (bindings.includes(binding)) { + return false; + } + if (binding.kind === 'derived') { const init = /** @type {CallExpression} */ (binding.initial); if (analysis.async_deriveds.has(init)) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js index 3146c6ef03..a4a1084da4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js @@ -141,7 +141,7 @@ export function Program(node, context) { const body = []; for (let i = 0; i < node.body.length; i++) { const transformed = /** @type {Program['body'][number]} */ (context.visit(node.body[i])); - const chunk = context.state.parallelized_derived_chunks?.at(-1); + const chunk = context.state.parallelized_chunks?.at(-1); body.push(transformed); if (chunk && chunk.position === i) { if (chunk.declarators.length === 1) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index 54641b14ac..8cf09c30ea 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -45,6 +45,52 @@ export function VariableDeclaration(node, context) { continue; } + if ( + init.type === 'AwaitExpression' && + context.state.analysis.instance?.scope === context.state.scope + ) { + const parallelize = can_be_parallelized( + init.argument, + context.state.scope, + context.state.analysis, + context.state.current_parallelized_chunk?.bindings ?? [] + ); + if (parallelize) { + const bindings = context.state.scope.get_bindings(declarator); + const visited = /** @type {VariableDeclarator} */ ( + context.visit({ + ...declarator, + init: init.argument + }) + ); + const declarators = [ + { + id: visited.id, + init: /** @type {Expression} */ (visited.init) + } + ]; + if ( + context.state.current_parallelized_chunk && + context.state.current_parallelized_chunk.kind === node.kind + ) { + context.state.current_parallelized_chunk.declarators.push(...declarators); + context.state.current_parallelized_chunk.bindings.push(...bindings); + context.state.current_parallelized_chunk.position = /** @type {Program} */ ( + context.path.at(-1) + ).body.indexOf(node); + } else { + const chunk = { + kind: node.kind, + declarators, + position: /** @type {Program} */ (context.path.at(-1)).body.indexOf(node), + bindings + }; + context.state.current_parallelized_chunk = chunk; + context.state.parallelized_chunks.push(chunk); + } + continue; + } + } declarations.push(/** @type {VariableDeclarator} */ (context.visit(declarator))); continue; } @@ -215,11 +261,18 @@ export function VariableDeclaration(node, context) { // TODO make it work without this declarator.id.type === 'Identifier' ) { - parallelize = can_be_parallelized(value, context.state.scope, context.state.analysis); + parallelize = can_be_parallelized( + value, + context.state.scope, + context.state.analysis, + context.state.current_parallelized_chunk?.bindings ?? [] + ); } /** @type {VariableDeclarator[]} */ const derived_declarators = []; + /** @type {Binding[]} */ + const bindings = []; if (declarator.id.type === 'Identifier') { let expression = /** @type {Expression} */ ( @@ -239,15 +292,13 @@ export function VariableDeclaration(node, context) { if (!parallelize) call = b.call(b.await(b.call('$.save', call))); if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name)); - + bindings.push(/** @type {Binding} */ (context.state.scope.get(declarator.id.name))); derived_declarators.push(b.declarator(declarator.id, call)); } else { if (rune === '$derived') expression = b.thunk(expression); let call = b.call('$.derived', expression); if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name)); - - derived_declarators.push(b.declarator(declarator.id, call)); } } else { const init = /** @type {CallExpression} */ (declarator.init); @@ -273,17 +324,13 @@ export function VariableDeclaration(node, context) { b.thunk(expression, true), location ? b.literal(location) : undefined ); - if (!parallelize) { - call = b.call(b.await(b.call('$.save', call))); - } + call = b.call(b.await(b.call('$.save', call))); } if (dev) { const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`; call = b.call('$.tag', call, b.literal(label)); } - - derived_declarators.push(b.declarator(id, call)); } const { inserts, paths } = extract_paths(declarator.id, rhs); @@ -299,8 +346,6 @@ export function VariableDeclaration(node, context) { const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`; call = b.call('$.tag', call, b.literal(label)); } - - derived_declarators.push(b.declarator(id, call)); } for (const path of paths) { @@ -327,19 +372,22 @@ export function VariableDeclaration(node, context) { })); if ( context.state.current_parallelized_chunk && - context.state.current_parallelized_chunk.kind === node.kind && - context.state.current_parallelized_chunk.position === - /** @type {Program} */ (context.path.at(-1)).body.indexOf(node) + context.state.current_parallelized_chunk.kind === node.kind ) { context.state.current_parallelized_chunk.declarators.push(...declarators); + context.state.current_parallelized_chunk.bindings.push(...bindings); + context.state.current_parallelized_chunk.position = /** @type {Program} */ ( + context.path.at(-1) + ).body.indexOf(node); } else { const chunk = { kind: node.kind, declarators, - position: /** @type {Program} */ (context.path.at(-1)).body.indexOf(node) + position: /** @type {Program} */ (context.path.at(-1)).body.indexOf(node), + bindings }; context.state.current_parallelized_chunk = chunk; - context.state.parallelized_derived_chunks.push(chunk); + context.state.parallelized_chunks.push(chunk); } }