Skip to content

Commit 9799b89

Browse files
committed
either fixed it or broke it, not sure yet
1 parent b82e182 commit 9799b89

File tree

2 files changed

+133
-14
lines changed

2 files changed

+133
-14
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,15 @@ export function should_proxy(node, scope) {
258258
}
259259
}
260260

261+
if (node.type === 'ObjectExpression' && scope !== null) {
262+
for (let property of node.properties) {
263+
if (property.type === 'Property') {
264+
// if there are any getters/setters, return false
265+
if (property.kind !== 'init') return false;
266+
}
267+
}
268+
}
269+
261270
return true;
262271
}
263272

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

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { ObjectExpression, Property, CallExpression, Expression, SpreadElement } from 'estree' */
1+
/** @import { ObjectExpression, Property, CallExpression, Expression, SpreadElement, Node, Identifier, PrivateIdentifier, Statement } from 'estree' */
22
/** @import { Context } from '../types' */
33
import * as b from '../../../../utils/builders.js';
44
import { get_rune } from '../../../scope.js';
@@ -10,12 +10,15 @@ import { walk } from 'zimmerframe';
1010
* @param {Context} context
1111
*/
1212
export function ObjectExpression(node, context) {
13+
/**
14+
* @typedef {[string, NonNullable<ReturnType<typeof get_rune>>, '$.state' | '$.derived', Expression, boolean]} ReactiveProperty
15+
*/
1316
let has_runes = false;
1417
/**
1518
* @type {Array<{rune: NonNullable<ReturnType<typeof get_rune>>, property: Property & {value: CallExpression}}>}
1619
*/
17-
let reactive_properties = [];
18-
let valid_property_runes = ['$state', '$derived', '$state.raw', '$derived.by'];
20+
const reactive_properties = [];
21+
const valid_property_runes = ['$state', '$derived', '$state.raw', '$derived.by'];
1922
for (let property of node.properties) {
2023
if (property.type !== 'Property') continue;
2124
const rune = get_rune(property.value, context.state.scope);
@@ -31,14 +34,61 @@ export function ObjectExpression(node, context) {
3134
context.next();
3235
return;
3336
}
34-
let body = [];
35-
let sources = new Map();
37+
/** @type {Statement[]} */
38+
const body = [];
39+
/** @type {Map<Property, ReactiveProperty>} */
40+
const sources = new Map();
3641
let has_this_reference = false;
3742
let counter = 0;
38-
let to_push = [];
43+
/** @type {Statement[]} */
44+
const before = [];
45+
/** @type {Statement[]} */
46+
const after = [];
47+
/** @type {string[]} */
48+
const declarations = [];
49+
/** @type {Map<string, Expression | undefined>} */
50+
const initial_declarations = new Map();
51+
// if a computed property is accessed, we treat it as if all of the object's properties have been accessed
52+
let all_are_referenced = false;
53+
/** @type {Set<any>} */
54+
const is_referenced = new Set();
55+
for (let property of node.properties) {
56+
walk(property, null, {
57+
//@ts-ignore
58+
FunctionExpression() {
59+
return;
60+
},
61+
//@ts-ignore
62+
FunctionDeclaration() {
63+
return;
64+
},
65+
ObjectExpression() {
66+
return;
67+
},
68+
/**
69+
*
70+
* @param {Node} node
71+
* @param {import('zimmerframe').Context<Node, null>} context
72+
*/
73+
ThisExpression(node, context) {
74+
const parent = context.path.at(-1);
75+
if (parent?.type === 'MemberExpression') {
76+
if (parent.computed) {
77+
all_are_referenced = true;
78+
} else {
79+
is_referenced.add(/** @type {Identifier | PrivateIdentifier} */ (parent.property).name);
80+
}
81+
}
82+
},
83+
ClassBody() {
84+
return;
85+
}
86+
});
87+
}
3988
for (let { rune, property } of reactive_properties) {
4089
const name = context.state.scope.generate(`$$${++counter}`);
4190
const call = rune.match(/^\$state/) ? '$.state' : '$.derived';
91+
let references_this = false;
4292
/** @type {Expression} */
4393
let value = /** @type {Expression} */ (context.visit(property.value.arguments[0] ?? b.void0));
4494
value = walk(value, null, {
@@ -54,6 +104,7 @@ export function ObjectExpression(node, context) {
54104
},
55105
ThisExpression() {
56106
has_this_reference = true;
107+
references_this = true;
57108
return b.id('$$object');
58109
},
59110
ClassBody() {
@@ -66,23 +117,76 @@ export function ObjectExpression(node, context) {
66117
: rune === '$state' && should_proxy(value, context.state.scope)
67118
? b.call('$.proxy', value)
68119
: value;
69-
sources.set(property, [name, rune]);
70-
to_push.push(b.let(name, b.call(call, value)));
120+
let key = property.computed
121+
? Symbol()
122+
: property.key.type === 'Literal'
123+
? property.key.value
124+
: /** @type {Identifier} */ (property.key).name;
125+
if (rune.match(/^\$state/) && !(all_are_referenced || is_referenced.has(key))) {
126+
let should_be_declared = false;
127+
walk(value, null, {
128+
CallExpression(node, context) {
129+
should_be_declared = true;
130+
context.stop();
131+
},
132+
MemberExpression(node, context) {
133+
should_be_declared = true;
134+
context.stop();
135+
}
136+
});
137+
if (should_be_declared) {
138+
const value_name = context.state.scope.generate('$$initial');
139+
initial_declarations.set(value_name, value);
140+
value = b.id(value_name);
141+
}
142+
}
143+
/** @type {ReactiveProperty} */
144+
const source = [
145+
name,
146+
rune,
147+
call,
148+
value,
149+
(value.type === 'Identifier' && initial_declarations.has(value.name)) || references_this
150+
];
151+
sources.set(property, source);
152+
if (references_this) {
153+
declarations.push(name);
154+
} else if (source[4]) {
155+
before.push(b.let(name, value));
156+
} else {
157+
before.push(b.let(name, b.call(call, value)));
158+
}
159+
}
160+
if (declarations.length) {
161+
before.push(
162+
b.declaration(
163+
'let',
164+
declarations.map((name) => b.declarator(name))
165+
)
166+
);
167+
}
168+
for (let [name, value] of initial_declarations) {
169+
after.push(b.let(name, value));
71170
}
72171
/** @type {(Property | SpreadElement)[]} */
73-
let properties = [];
172+
const properties = [];
74173
for (let property of node.properties) {
75174
if (property.type === 'SpreadElement') {
76175
properties.push(/** @type {SpreadElement} */ (context.visit(property)));
77176
continue;
78177
}
79178
if (sources.has(property)) {
80-
let [name, rune] = sources.get(property);
179+
const [name, rune, call, value, initially_declared] = /** @type {ReactiveProperty} */ (
180+
sources.get(property)
181+
);
182+
let maybe_assign = initially_declared
183+
? b.assignment('??=', b.id(name), b.call(call, value))
184+
: b.id(name);
81185
properties.push(
82186
b.prop(
83187
'get',
84188
/** @type {Expression} */ (context.visit(/**@type {Expression} */ (property.key))),
85-
b.function(null, [], b.block([b.return(b.call('$.get', b.id(name)))])),
189+
b.function(null, [], b.block([b.return(b.call('$.get', maybe_assign))])),
86190
property.computed
87191
),
88192
b.prop(
@@ -93,7 +197,12 @@ export function ObjectExpression(node, context) {
93197
[b.id('$$value')],
94198
b.block([
95199
b.stmt(
96-
b.call('$.set', b.id(name), b.id('$$value'), rune === '$state' ? b.true : undefined)
200+
b.call(
201+
'$.set',
202+
maybe_assign,
203+
b.id('$$value'),
204+
rune === '$state' ? b.true : undefined
205+
)
97206
)
98207
])
99208
),
@@ -105,11 +214,12 @@ export function ObjectExpression(node, context) {
105214
}
106215
}
107216
if (has_this_reference) {
217+
body.push(...before);
108218
body.push(b.let('$$object', b.object(properties)));
109-
body.push(...to_push);
219+
body.push(...after);
110220
body.push(b.return(b.id('$$object')));
111221
} else {
112-
body.push(...to_push);
222+
body.push(...before, ...after);
113223
body.push(b.return(b.object(properties)));
114224
}
115225
return b.call(b.arrow([], b.block(body)));

0 commit comments

Comments
 (0)