diff --git a/.changeset/sweet-brooms-wonder.md b/.changeset/sweet-brooms-wonder.md new file mode 100644 index 000000000000..557ce89e9ed0 --- /dev/null +++ b/.changeset/sweet-brooms-wonder.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: allow runes in POJO properties diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 2eac934b332c..2aba9f3c41c8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -118,7 +118,8 @@ export function CallExpression(node, context) { if ( (parent.type !== 'VariableDeclarator' || get_parent(context.path, -3).type === 'ConstTag') && - !(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) + !(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) && + !(parent.type === 'Property' && parent.value === node) ) { e.state_invalid_placement(node, rune); } 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 0bdfbae746d0..8c60596e99af 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 @@ -36,6 +36,7 @@ import { KeyBlock } from './visitors/KeyBlock.js'; import { LabeledStatement } from './visitors/LabeledStatement.js'; import { LetDirective } from './visitors/LetDirective.js'; import { MemberExpression } from './visitors/MemberExpression.js'; +import { ObjectExpression } from './visitors/ObjectExpression.js'; import { OnDirective } from './visitors/OnDirective.js'; import { Program } from './visitors/Program.js'; import { RegularElement } from './visitors/RegularElement.js'; @@ -111,6 +112,7 @@ const visitors = { LabeledStatement, LetDirective, MemberExpression, + ObjectExpression, OnDirective, Program, RegularElement, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ObjectExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ObjectExpression.js new file mode 100644 index 000000000000..d2d798d8a816 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ObjectExpression.js @@ -0,0 +1,73 @@ +/** @import { ObjectExpression, Property, CallExpression, Expression, SpreadElement, Statement } from 'estree' */ +/** @import { Context } from '../types' */ +import * as b from '../../../../utils/builders.js'; +import { get_rune } from '../../../scope.js'; +import { should_proxy } from '../utils.js'; + +/** + * @param {ObjectExpression} node + * @param {Context} context + */ +export function ObjectExpression(node, context) { + let has_runes = false; + const valid_property_runes = ['$state', '$derived', '$state.raw', '$derived.by']; + /** @type {Statement[]} */ + const body = []; + let counter = 0; + /** @type {(Property | SpreadElement)[]} */ + const properties = []; + for (let property of node.properties) { + if (property.type !== 'Property') { + properties.push(/** @type {SpreadElement} */ (context.visit(property))); + continue; + } + const rune = get_rune(property.value, context.state.scope); + if (rune && valid_property_runes.includes(rune)) { + has_runes = true; + const name = context.state.scope.generate(`$$${++counter}`); + const call = rune.match(/^\$state/) ? '$.state' : '$.derived'; + /** @type {Expression} */ + let value = /** @type {Expression} */ ( + context.visit(/** @type {CallExpression} */ (property.value).arguments[0] ?? b.void0) + ); + const key = /** @type {Expression} */ (context.visit(property.key)); + value = + rune === '$derived' + ? b.thunk(value) + : rune === '$state' && should_proxy(value, context.state.scope) + ? b.call('$.proxy', value) + : value; + properties.push( + b.prop( + 'get', + key, + b.function(null, [], b.block([b.return(b.call('$.get', b.id(name)))])), + property.computed + ), + b.prop( + 'set', + key, + b.function( + null, + [b.id('$$value')], + b.block([ + b.stmt( + b.call('$.set', b.id(name), b.id('$$value'), rune === '$state' ? b.true : undefined) + ) + ]) + ), + property.computed + ) + ); + body.push(b.let(name, b.call(call, value))); + } else { + properties.push(/** @type {Property} */ (context.visit(property))); + } + } + if (!has_runes) { + context.next(); + return; + } + body.push(b.return(b.object(properties))); + return b.call(b.arrow([], b.block(body))); +}