diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index 25a71eed86..0ded021cf7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -1,6 +1,6 @@ import type { Scope } from '../scope.js'; import type { ComponentAnalysis, ReactiveStatement } from '../types.js'; -import type { AST, ExpressionMetadata, StateFields, ValidatedCompileOptions } from '#compiler'; +import type { AST, StateFields, ValidatedCompileOptions } from '#compiler'; import type { ExpressionMetadata } from '../nodes.js'; export interface AnalysisState { 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 2adafafc42..e3f05dd36f 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 @@ -47,6 +47,7 @@ import { RenderTag } from './visitors/RenderTag.js'; import { SlotElement } from './visitors/SlotElement.js'; import { SnippetBlock } from './visitors/SnippetBlock.js'; import { SpreadAttribute } from './visitors/SpreadAttribute.js'; +import { StaticBlock } from './visitors/StaticBlock.js'; import { SvelteBody } from './visitors/SvelteBody.js'; import { SvelteComponent } from './visitors/SvelteComponent.js'; import { SvelteDocument } from './visitors/SvelteDocument.js'; @@ -127,6 +128,7 @@ const visitors = { SlotElement, SnippetBlock, SpreadAttribute, + StaticBlock, SvelteBody, SvelteComponent, SvelteDocument, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js index d1c0978a81..607cc8e2d3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js @@ -1,4 +1,4 @@ -/** @import { ArrowFunctionExpression, BlockStatement, Expression, FunctionDeclaration, FunctionExpression, Statement } from 'estree' */ +/** @import { ArrowFunctionExpression, BlockStatement, Declaration, Expression, FunctionDeclaration, FunctionExpression, Statement, VariableDeclaration } from 'estree' */ /** @import { ComponentContext } from '../types' */ import { add_state_transformers } from './shared/declarations.js'; import * as b from '#compiler/builders'; @@ -11,6 +11,24 @@ export function BlockStatement(node, context) { add_state_transformers(context); const tracing = context.state.scope.tracing; + /** @type {BlockStatement['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + if (tracing !== null) { const parent = /** @type {ArrowFunctionExpression | FunctionDeclaration | FunctionExpression} */ ( @@ -22,11 +40,10 @@ export function BlockStatement(node, context) { const call = b.call( '$.trace', /** @type {Expression} */ (tracing), - b.thunk(b.block(node.body.map((n) => /** @type {Statement} */ (context.visit(n)))), is_async) + b.thunk(b.block(body), is_async) ); return b.block([b.return(is_async ? b.await(call) : call)]); } - - context.next(); + return b.block(body); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index 14e8b61e42..018ecc597f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -35,23 +35,40 @@ export function ClassBody(node, context) { ) { continue; } + const member = b.member(b.this, field.key); + const should_proxy = field.type === '$state' && true; // TODO if (typeof name === 'number' && field.computed_key) { + const computed_key = /** @type {Expression} */ (context.visit(field.computed_key)); + const evaluation = context.state.scope.evaluate(computed_key); + if (evaluation.is_known) { + body.push( + b.prop_def(field.key, null), + b.method( + 'get', + b.literal(evaluation.value), + [], + [b.return(b.call('$.get', member))], + true + ), + b.method( + 'set', + b.literal(evaluation.value), + [b.id('value')], + [b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); - const member = b.member(b.this, field.key); - - const should_proxy = field.type === '$state' && true; // TODO body.push( b.prop_def(field.key, null), b.method( 'get', - b.assignment( - '=', - b.id(key), - /** @type {Expression} */ (context.visit(field.computed_key)) - ), + b.assignment('=', b.id(key), computed_key), [], [b.return(b.call('$.get', member))], true @@ -66,11 +83,6 @@ export function ClassBody(node, context) { ); continue; } - - const member = b.member(b.this, field.key); - - const should_proxy = field.type === '$state' && true; // TODO - const key = b.key(/** @type {string} */ (name)); body.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassDeclaration.js index 96bd5c3d29..630b3fbd66 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassDeclaration.js @@ -17,16 +17,25 @@ export function ClassDeclaration(node, context) { : null; const body = /** @type {ClassBody} */ (context.visit(node.body, state)); if (state.computed_field_declarations.length > 0) { - const init = b.call( - b.arrow( - [], - b.block([ - ...state.computed_field_declarations, - b.return(b.class(node.id, body, super_class)) - ]) - ) - ); - return node.id ? b.var(node.id, init) : init; + if (context.path.at(-1)?.type === 'ExportDefaultDeclaration') { + const init = b.call( + b.arrow( + [], + b.block([ + ...state.computed_field_declarations, + b.return(b.class(node.id, body, super_class)) + ]) + ) + ); + return node.id ? b.var(node.id, init) : init; + } else { + return { + ...b.class_declaration(node.id, body, super_class), + metadata: { + computed_field_declarations: state.computed_field_declarations + } + } + } } return b.class_declaration(node.id, body, super_class); } 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 07342da314..7183ac135f 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 @@ -1,14 +1,14 @@ -/** @import { Expression, ImportDeclaration, MemberExpression, Program } from 'estree' */ +/** @import { Declaration, Expression, ImportDeclaration, MemberExpression, Program, Statement, VariableDeclaration } from 'estree' */ /** @import { ComponentContext } from '../types' */ import { build_getter, is_prop_source } from '../utils.js'; import * as b from '#compiler/builders'; import { add_state_transformers } from './shared/declarations.js'; /** - * @param {Program} _ + * @param {Program} node * @param {ComponentContext} context */ -export function Program(_, context) { +export function Program(node, context) { if (!context.state.analysis.runes) { context.state.transform['$$props'] = { read: (node) => ({ ...node, name: '$$sanitized_props' }) @@ -137,5 +137,26 @@ export function Program(_, context) { add_state_transformers(context); - context.next(); + /** @type {Program['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + + return { + ...node, + body + }; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/StaticBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/StaticBlock.js new file mode 100644 index 0000000000..d578f69406 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/StaticBlock.js @@ -0,0 +1,31 @@ +/** @import { Declaration, Statement, StaticBlock, VariableDeclaration } from 'estree' */ +/** @import { ComponentContext } from '../types' */ + +/** + * @param {StaticBlock} node + * @param {ComponentContext} context + */ +export function StaticBlock(node, context) { + /** @type {StaticBlock['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + + return { + ...node, + body + }; +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 16ca48d9be..558f9ca305 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -11,6 +11,7 @@ import { render_stylesheet } from '../css/index.js'; import { AssignmentExpression } from './visitors/AssignmentExpression.js'; import { AwaitBlock } from './visitors/AwaitBlock.js'; import { AwaitExpression } from './visitors/AwaitExpression.js'; +import { BlockStatement } from './visitors/BlockStatement.js'; import { CallExpression } from './visitors/CallExpression.js'; import { ClassBody } from './visitors/ClassBody.js'; import { ClassDeclaration } from './visitors/ClassDeclaration.js'; @@ -27,12 +28,14 @@ import { IfBlock } from './visitors/IfBlock.js'; import { KeyBlock } from './visitors/KeyBlock.js'; import { LabeledStatement } from './visitors/LabeledStatement.js'; import { MemberExpression } from './visitors/MemberExpression.js'; +import { Program } from './visitors/Program.js'; import { PropertyDefinition } from './visitors/PropertyDefinition.js'; import { RegularElement } from './visitors/RegularElement.js'; import { RenderTag } from './visitors/RenderTag.js'; import { SlotElement } from './visitors/SlotElement.js'; import { SnippetBlock } from './visitors/SnippetBlock.js'; import { SpreadAttribute } from './visitors/SpreadAttribute.js'; +import { StaticBlock } from './visitors/StaticBlock.js'; import { SvelteComponent } from './visitors/SvelteComponent.js'; import { SvelteElement } from './visitors/SvelteElement.js'; import { SvelteFragment } from './visitors/SvelteFragment.js'; @@ -49,6 +52,7 @@ const global_visitors = { _: set_scope, AssignmentExpression, AwaitExpression, + BlockStatement, CallExpression, ClassBody, ClassDeclaration, @@ -57,7 +61,9 @@ const global_visitors = { Identifier, LabeledStatement, MemberExpression, + Program, PropertyDefinition, + StaticBlock, UpdateExpression, VariableDeclaration }; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/BlockStatement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/BlockStatement.js new file mode 100644 index 0000000000..8001fffb83 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/BlockStatement.js @@ -0,0 +1,31 @@ +/** @import { BlockStatement, Declaration, Statement, VariableDeclaration } from 'estree' */ +/** @import { Context } from '../types' */ + +/** + * @param {BlockStatement} node + * @param {Context} context + */ +export function BlockStatement(node, context) { + /** @type {BlockStatement['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + + return { + ...node, + body + }; +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js index 9e5c3d1c45..d3c04af9ca 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js @@ -32,10 +32,10 @@ export function ClassBody(node, context) { continue; } + const member = b.member(b.this, field.key); if (typeof name !== 'string' && field.computed_key) { const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); - const member = b.member(b.this, field.key); body.push( b.prop_def(field.key, null), b.method( @@ -62,8 +62,6 @@ export function ClassBody(node, context) { // insert backing fields for stuff declared in the constructor if (field.type === '$derived' || field.type === '$derived.by') { - const member = b.member(b.this, field.key); - body.push( b.prop_def(field.key, null), b.method('get', b.key(/** @type {string} */ (name)), [], [b.return(b.call(member))]), @@ -95,6 +93,7 @@ export function ClassBody(node, context) { body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state))); continue; } + const member = b.member(b.this, field.key); if ( (typeof name === 'string' && name[0] === '#') || @@ -104,8 +103,6 @@ export function ClassBody(node, context) { body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state))); } else if (field.node === definition && typeof name === 'string') { // $derived / $derived.by - const member = b.member(b.this, field.key); - body.push( b.prop_def( field.key, @@ -117,7 +114,25 @@ export function ClassBody(node, context) { ); } else if (field.computed_key) { // $derived / $derived.by - const member = b.member(b.this, field.key); + const computed_key = /** @type {Expression} */ (context.visit(field.computed_key)); + const evaluation = context.state.scope.evaluate(computed_key); + if (evaluation.is_known) { + body.push( + b.prop_def( + field.key, + /** @type {CallExpression} */ (context.visit(field.value, child_state)) + ), + b.method('get', b.literal(evaluation.value), [], [b.return(b.call(member))], true), + b.method( + 'set', + b.literal(evaluation.value), + [b.id('$$value')], + [b.return(b.call(member, b.id('$$value')))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); body.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassDeclaration.js index dfacea85f8..dbe9c8a787 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassDeclaration.js @@ -17,16 +17,25 @@ export function ClassDeclaration(node, context) { : null; const body = /** @type {ClassBody} */ (context.visit(node.body, state)); if (state.computed_field_declarations.length > 0) { - const init = b.call( - b.arrow( - [], - b.block([ - ...state.computed_field_declarations, - b.return(b.class(node.id, body, super_class)) - ]) - ) - ); - return node.id ? b.var(node.id, init) : init; + if (context.path.at(-1)?.type === 'ExportDefaultDeclaration') { + const init = b.call( + b.arrow( + [], + b.block([ + ...state.computed_field_declarations, + b.return(b.class(node.id, body, super_class)) + ]) + ) + ); + return node.id ? b.var(node.id, init) : init; + } else { + return { + ...b.class_declaration(node.id, body, super_class), + metadata: { + computed_field_declarations: state.computed_field_declarations + } + }; + } } return b.class_declaration(node.id, body, super_class); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js new file mode 100644 index 0000000000..7ad1a5d946 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js @@ -0,0 +1,31 @@ +/** @import { Declaration, Program, Statement, VariableDeclaration } from 'estree' */ +/** @import { Context } from '../types.js' */ + +/** + * @param {Program} node + * @param {Context} context + */ +export function Program(node, context) { + /** @type {Program['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + + return { + ...node, + body + }; +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/StaticBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/StaticBlock.js new file mode 100644 index 0000000000..d5c8c3b87c --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/StaticBlock.js @@ -0,0 +1,31 @@ +/** @import { Declaration, Statement, StaticBlock, VariableDeclaration } from 'estree' */ +/** @import { Context } from '../types' */ + +/** + * @param {StaticBlock} node + * @param {Context} context + */ +export function StaticBlock(node, context) { + /** @type {StaticBlock['body']} */ + const body = []; + for (const child of node.body) { + const visited = /** @type {Declaration | Statement} */ (context.visit(child)); + if ( + visited.type === 'ClassDeclaration' && + 'metadata' in visited && + visited.metadata !== null && + typeof visited.metadata === 'object' && + 'computed_field_declarations' in visited.metadata + ) { + body.push( + .../** @type {VariableDeclaration[]} */ (visited.metadata.computed_field_declarations) + ); + } + body.push(visited); + } + + return { + ...node, + body + }; +} diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 84c3787b47..cfddf51681 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -1,4 +1,4 @@ -import type { AST, Binding, StateField, StateFields } from '#compiler'; +import type { AST, Binding, StateFields } from '#compiler'; import type { AwaitExpression, CallExpression,