Skip to content

Commit 9f61a3b

Browse files
committed
working
1 parent 5b49610 commit 9f61a3b

File tree

11 files changed

+186
-92
lines changed

11 files changed

+186
-92
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,22 @@ export function EachBlock(node, context) {
234234
} else if (node.context) {
235235
const unwrapped = (flags & EACH_ITEM_REACTIVE) !== 0 ? b.call('$.get', item) : item;
236236

237-
const { paths } = extract_paths(node.context, unwrapped);
237+
const { inserts, paths } = extract_paths(node.context, unwrapped);
238+
239+
for (const { id, value } of inserts) {
240+
id.name = context.state.scope.generate('$$array');
241+
child_state.transform[id.name] = { read: get_value };
242+
243+
declarations.push(
244+
b.var(
245+
id,
246+
b.call(
247+
'$.derived',
248+
/** @type {Expression} */ (context.visit(b.thunk(value), child_state))
249+
)
250+
)
251+
);
252+
}
238253

239254
for (const path of paths) {
240255
const name = /** @type {Identifier} */ (path.node).name;

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,16 @@ export function SnippetBlock(node, context) {
4343
let arg_alias = `$$arg${i}`;
4444
args.push(b.id(arg_alias));
4545

46-
const { paths } = extract_paths(argument, b.maybe_call(b.id(arg_alias)));
46+
const { inserts, paths } = extract_paths(argument, b.maybe_call(b.id(arg_alias)));
47+
48+
for (const { id, value } of inserts) {
49+
id.name = context.state.scope.generate('$$array');
50+
transform[id.name] = { read: get_value };
51+
52+
declarations.push(
53+
b.var(id, b.call('$.derived', /** @type {Expression} */ (context.visit(b.thunk(value)))))
54+
);
55+
}
4756

4857
for (const path of paths) {
4958
const name = /** @type {Identifier} */ (path.node).name;

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

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as assert from '../../../../utils/assert.js';
88
import { get_rune } from '../../../scope.js';
99
import { get_prop_source, is_prop_source, is_state_source, should_proxy } from '../utils.js';
1010
import { is_hoisted_function } from '../../utils.js';
11+
import { get_value } from './shared/declarations.js';
1112

1213
/**
1314
* @param {VariableDeclaration} node
@@ -116,7 +117,7 @@ export function VariableDeclaration(node, context) {
116117
}
117118

118119
const args = /** @type {CallExpression} */ (init).arguments;
119-
const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0;
120+
const value = /** @type {Expression} */ (args[0]) ?? b.void0; // TODO do we need the void 0? can we just omit it altogether?
120121

121122
if (rune === '$state' || rune === '$state.raw') {
122123
/**
@@ -138,15 +139,31 @@ export function VariableDeclaration(node, context) {
138139

139140
if (declarator.id.type === 'Identifier') {
140141
declarations.push(
141-
b.declarator(declarator.id, create_state_declarator(declarator.id, value))
142+
b.declarator(
143+
declarator.id,
144+
create_state_declarator(
145+
declarator.id,
146+
/** @type {Expression} */ (context.visit(value))
147+
)
148+
)
142149
);
143150
} else {
144151
const tmp = b.id(context.state.scope.generate('tmp'));
145-
const { paths } = extract_paths(declarator.id, tmp);
152+
const { inserts, paths } = extract_paths(declarator.id, tmp);
153+
146154
declarations.push(
147155
b.declarator(tmp, value),
156+
...inserts.map(({ id, value }) => {
157+
id.name = context.state.scope.generate('$$array');
158+
context.state.transform[id.name] = { read: get_value };
159+
160+
return b.declarator(
161+
id,
162+
b.call('$.derived', /** @type {Expression} */ (context.visit(b.thunk(value))))
163+
);
164+
}),
148165
...paths.map((path) => {
149-
const value = path.expression;
166+
const value = /** @type {Expression} */ (context.visit(path.expression));
150167
const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
151168
return b.declarator(
152169
path.node,
@@ -163,12 +180,10 @@ export function VariableDeclaration(node, context) {
163180

164181
if (rune === '$derived' || rune === '$derived.by') {
165182
if (declarator.id.type === 'Identifier') {
166-
declarations.push(
167-
b.declarator(
168-
declarator.id,
169-
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
170-
)
171-
);
183+
let expression = /** @type {Expression} */ (context.visit(value));
184+
if (rune === '$derived') expression = b.thunk(expression);
185+
186+
declarations.push(b.declarator(declarator.id, b.call('$.derived', expression)));
172187
} else {
173188
const init = /** @type {CallExpression} */ (declarator.init);
174189

@@ -178,24 +193,32 @@ export function VariableDeclaration(node, context) {
178193
const id = b.id(context.state.scope.generate('$$d'));
179194
rhs = b.call('$.get', id);
180195

181-
declarations.push(
182-
b.declarator(id, b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value)))
183-
);
196+
let expression = /** @type {Expression} */ (context.visit(value));
197+
if (rune === '$derived') expression = b.thunk(expression);
198+
199+
declarations.push(b.declarator(id, b.call('$.derived', expression)));
184200
}
185201

186202
const { inserts, paths } = extract_paths(declarator.id, rhs);
187203

188-
for (const insert of inserts) {
189-
insert.id.name = context.state.scope.generate('$$array');
190-
declarations.push(b.declarator(insert.id, insert.value));
191-
}
204+
for (const { id, value } of inserts) {
205+
id.name = context.state.scope.generate('$$array');
206+
context.state.transform[id.name] = { read: get_value };
192207

193-
for (const path of paths) {
194208
declarations.push(
195-
b.declarator(path.node, b.call('$.derived', b.thunk(path.expression)))
209+
b.declarator(
210+
id,
211+
b.call('$.derived', /** @type {Expression} */ (context.visit(b.thunk(value))))
212+
)
196213
);
197214
}
215+
216+
for (const path of paths) {
217+
const expression = /** @type {Expression} */ (context.visit(path.expression));
218+
declarations.push(b.declarator(path.node, b.call('$.derived', b.thunk(expression))));
219+
}
198220
}
221+
199222
continue;
200223
}
201224
}
@@ -225,7 +248,7 @@ export function VariableDeclaration(node, context) {
225248
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
226249
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
227250
const tmp = b.id(context.state.scope.generate('tmp'));
228-
const { paths } = extract_paths(declarator.id, tmp);
251+
const { inserts, paths } = extract_paths(declarator.id, tmp);
229252

230253
declarations.push(
231254
b.declarator(
@@ -234,10 +257,23 @@ export function VariableDeclaration(node, context) {
234257
)
235258
);
236259

260+
for (const { id, value } of inserts) {
261+
id.name = context.state.scope.generate('$$array');
262+
context.state.transform[id.name] = { read: get_value };
263+
264+
declarations.push(
265+
b.declarator(
266+
id,
267+
b.call('$.derived', /** @type {Expression} */ (context.visit(b.thunk(value))))
268+
)
269+
);
270+
}
271+
237272
for (const path of paths) {
238273
const name = /** @type {Identifier} */ (path.node).name;
239274
const binding = /** @type {Binding} */ (context.state.scope.get(name));
240-
const value = path.expression;
275+
const value = /** @type {Expression} */ (context.visit(path.expression));
276+
241277
declarations.push(
242278
b.declarator(
243279
path.node,
@@ -271,7 +307,7 @@ export function VariableDeclaration(node, context) {
271307
declarations.push(
272308
...create_state_declarators(
273309
declarator,
274-
context.state,
310+
context,
275311
/** @type {Expression} */ (declarator.init && context.visit(declarator.init))
276312
)
277313
);
@@ -291,30 +327,41 @@ export function VariableDeclaration(node, context) {
291327
/**
292328
* Creates the output for a state declaration in legacy mode.
293329
* @param {VariableDeclarator} declarator
294-
* @param {ComponentClientTransformState} scope
330+
* @param {ComponentContext} context
295331
* @param {Expression} value
296332
*/
297-
function create_state_declarators(declarator, { scope, analysis }, value) {
333+
function create_state_declarators(declarator, context, value) {
298334
if (declarator.id.type === 'Identifier') {
299335
return [
300336
b.declarator(
301337
declarator.id,
302-
b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
338+
b.call('$.mutable_source', value, context.state.analysis.immutable ? b.true : undefined)
303339
)
304340
];
305341
}
306342

307-
const tmp = b.id(scope.generate('tmp'));
308-
const { paths } = extract_paths(declarator.id, tmp);
343+
const tmp = b.id(context.state.scope.generate('tmp'));
344+
const { inserts, paths } = extract_paths(declarator.id, tmp);
345+
309346
return [
310347
b.declarator(tmp, value),
348+
...inserts.map(({ id, value }) => {
349+
id.name = context.state.scope.generate('$$array');
350+
context.state.transform[id.name] = { read: get_value };
351+
352+
return b.declarator(
353+
id,
354+
b.call('$.derived', /** @type {Expression} */ (context.visit(b.thunk(value))))
355+
);
356+
}),
311357
...paths.map((path) => {
312-
const value = path.expression;
313-
const binding = scope.get(/** @type {Identifier} */ (path.node).name);
358+
const value = /** @type {Expression} */ (context.visit(path.expression));
359+
const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
360+
314361
return b.declarator(
315362
path.node,
316363
binding?.kind === 'state'
317-
? b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
364+
? b.call('$.mutable_source', value, context.state.analysis.immutable ? b.true : undefined)
318365
: value
319366
);
320367
})

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,28 @@ export function VariableDeclaration(node, context) {
121121
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
122122
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
123123
const tmp = b.id(context.state.scope.generate('tmp'));
124-
const { paths } = extract_paths(declarator.id, tmp);
124+
const { inserts, paths } = extract_paths(declarator.id, tmp);
125+
125126
declarations.push(
126127
b.declarator(
127128
tmp,
128129
/** @type {Expression} */ (context.visit(/** @type {Expression} */ (declarator.init)))
129130
)
130131
);
132+
133+
for (const { id, value } of inserts) {
134+
id.name = context.state.scope.generate('$$array');
135+
declarations.push(b.declarator(id, value));
136+
}
137+
131138
for (const path of paths) {
132139
const value = path.expression;
133140
const name = /** @type {Identifier} */ (path.node).name;
134141
const binding = /** @type {Binding} */ (context.state.scope.get(name));
135142
const prop = b.member(b.id('$$props'), b.literal(binding.prop_alias ?? name), true);
136143
declarations.push(b.declarator(path.node, build_fallback(prop, value)));
137144
}
145+
138146
continue;
139147
}
140148

packages/svelte/src/compiler/phases/3-transform/shared/assignments.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
1+
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern, Statement } from 'estree' */
22
/** @import { Context as ClientContext } from '../client/types.js' */
33
/** @import { Context as ServerContext } from '../server/types.js' */
44
import { extract_paths, is_expression_async } from '../../../utils/ast.js';
55
import * as b from '#compiler/builders';
6+
import { get_value } from '../client/visitors/shared/declarations.js';
67

78
/**
89
* @template {ClientContext | ServerContext} Context
@@ -23,7 +24,11 @@ export function visit_assignment_expression(node, context, build_assignment) {
2324

2425
let changed = false;
2526

26-
const { paths } = extract_paths(node.left, rhs);
27+
const { inserts, paths } = extract_paths(node.left, rhs);
28+
29+
for (const { id } of inserts) {
30+
id.name = context.state.scope.generate('$$array');
31+
}
2732

2833
const assignments = paths.map((path) => {
2934
const value = path.expression;
@@ -47,16 +52,20 @@ export function visit_assignment_expression(node, context, build_assignment) {
4752
}
4853

4954
const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
50-
const sequence = b.sequence(assignments);
5155

52-
if (!is_standalone) {
53-
// this is part of an expression, we need the sequence to end with the value
54-
sequence.expressions.push(rhs);
55-
}
56+
if (inserts.length > 0 || should_cache) {
57+
/** @type {Statement[]} */
58+
const statements = [
59+
...inserts.map(({ id, value }) => b.var(id, value)),
60+
...assignments.map(b.stmt)
61+
];
62+
63+
if (!is_standalone) {
64+
// this is part of an expression, we need the sequence to end with the value
65+
statements.push(b.return(rhs));
66+
}
5667

57-
if (should_cache) {
58-
// the right hand side is a complex expression, wrap in an IIFE to cache it
59-
const iife = b.arrow([rhs], sequence);
68+
const iife = b.arrow([rhs], b.block(statements));
6069

6170
const iife_is_async =
6271
is_expression_async(value) ||
@@ -65,6 +74,13 @@ export function visit_assignment_expression(node, context, build_assignment) {
6574
return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
6675
}
6776

77+
const sequence = b.sequence(assignments);
78+
79+
if (!is_standalone) {
80+
// this is part of an expression, we need the sequence to end with the value
81+
sequence.expressions.push(rhs);
82+
}
83+
6884
return sequence;
6985
}
7086

packages/svelte/src/compiler/utils/ast.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,22 +341,19 @@ function _extract_paths(paths, inserts, param, expression, update_expression, ha
341341
// the consumer is responsible for setting the name of the identifier
342342
const id = b.id('#');
343343

344-
const is_rest = param.elements.at(-1)?.type === 'RestElement';
345-
const call = b.call(
344+
const value = b.call(
346345
'$.to_array',
347346
expression,
348-
is_rest ? undefined : b.literal(param.elements.length)
347+
param.elements.at(-1)?.type === 'RestElement' ? undefined : b.literal(param.elements.length)
349348
);
350349

351-
inserts.push({ id, value: b.call('$.derived', b.thunk(call)) });
352-
353-
const array = b.call('$.get', id);
350+
inserts.push({ id, value });
354351

355352
for (let i = 0; i < param.elements.length; i += 1) {
356353
const element = param.elements[i];
357354
if (element) {
358355
if (element.type === 'RestElement') {
359-
const rest_expression = b.call(b.member(array, 'slice'), b.literal(i));
356+
const rest_expression = b.call(b.member(id, 'slice'), b.literal(i));
360357

361358
if (element.argument.type === 'Identifier') {
362359
paths.push({
@@ -377,7 +374,7 @@ function _extract_paths(paths, inserts, param, expression, update_expression, ha
377374
);
378375
}
379376
} else {
380-
const array_expression = b.member(array, b.literal(i), true);
377+
const array_expression = b.member(id, b.literal(i), true);
381378

382379
_extract_paths(
383380
paths,

0 commit comments

Comments
 (0)