Skip to content

Commit ed78fed

Browse files
committed
Merge branch 'main' into proxy-parent
2 parents f6b5553 + 30fa876 commit ed78fed

File tree

36 files changed

+426
-216
lines changed

36 files changed

+426
-216
lines changed

.changeset/clever-sloths-push.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
breaking: remove unstate(), replace with $state.snapshot rune

.changeset/pre.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"clean-eels-beg",
5353
"clever-chefs-relate",
5454
"clever-rockets-burn",
55+
"clever-sloths-push",
5556
"cold-birds-own",
5657
"cold-masks-learn",
5758
"cool-ants-leave",
@@ -285,6 +286,7 @@
285286
"rotten-rules-invite",
286287
"rude-ghosts-tickle",
287288
"selfish-dragons-knock",
289+
"selfish-socks-smile",
288290
"selfish-spies-help",
289291
"selfish-tools-hide",
290292
"serious-gorillas-eat",

.changeset/selfish-socks-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: more accurate default value handling

packages/svelte/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# svelte
22

3+
## 5.0.0-next.105
4+
5+
### Patch Changes
6+
7+
- breaking: remove unstate(), replace with $state.snapshot rune ([#11180](https://github.com/sveltejs/svelte/pull/11180))
8+
9+
- fix: more accurate default value handling ([#11183](https://github.com/sveltejs/svelte/pull/11183))
10+
311
## 5.0.0-next.104
412

513
### Patch Changes

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.0.0-next.104",
5+
"version": "5.0.0-next.105",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/ambient.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,26 @@ declare namespace $state {
4242
*/
4343
export function frozen<T>(initial: T): Readonly<T>;
4444
export function frozen<T>(): Readonly<T> | undefined;
45+
/**
46+
* To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:
47+
*
48+
* Example:
49+
* ```ts
50+
* <script>
51+
* let counter = $state({ count: 0 });
52+
*
53+
* function onclick() {
54+
* // Will log `{ count: ... }` rather than `Proxy { ... }`
55+
* console.log($state.snapshot(counter));
56+
* };
57+
* </script>
58+
* ```
59+
*
60+
* https://svelte-5-preview.vercel.app/docs/runes#$state.snapshot
61+
*
62+
* @param state The value to snapshot
63+
*/
64+
export function snapshot<T>(state: T): T;
4565
}
4666

4767
/**

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,12 @@ function validate_call_expression(node, scope, path) {
865865
error(node, 'invalid-rune-args-length', rune, [1]);
866866
}
867867
}
868+
869+
if (rune === '$state.snapshot') {
870+
if (node.arguments.length !== 1) {
871+
error(node, 'invalid-rune-args-length', rune, [1]);
872+
}
873+
}
868874
}
869875

870876
/**

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

Lines changed: 6 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import * as b from '../../../utils/builders.js';
2-
import { extract_paths, is_simple_expression, object } from '../../../utils/ast.js';
2+
import {
3+
extract_paths,
4+
is_expression_async,
5+
is_simple_expression,
6+
object
7+
} from '../../../utils/ast.js';
38
import { error } from '../../../errors.js';
49
import {
510
PROPS_IS_LAZY_INITIAL,
@@ -115,103 +120,6 @@ export function serialize_get_binding(node, state) {
115120
return node;
116121
}
117122

118-
/**
119-
* @param {import('estree').Expression | import('estree').Pattern} expression
120-
* @returns {boolean}
121-
*/
122-
function is_expression_async(expression) {
123-
switch (expression.type) {
124-
case 'AwaitExpression': {
125-
return true;
126-
}
127-
case 'ArrayPattern': {
128-
return expression.elements.some((element) => element && is_expression_async(element));
129-
}
130-
case 'ArrayExpression': {
131-
return expression.elements.some((element) => {
132-
if (!element) {
133-
return false;
134-
} else if (element.type === 'SpreadElement') {
135-
return is_expression_async(element.argument);
136-
} else {
137-
return is_expression_async(element);
138-
}
139-
});
140-
}
141-
case 'AssignmentPattern':
142-
case 'AssignmentExpression':
143-
case 'BinaryExpression':
144-
case 'LogicalExpression': {
145-
return is_expression_async(expression.left) || is_expression_async(expression.right);
146-
}
147-
case 'CallExpression':
148-
case 'NewExpression': {
149-
return (
150-
(expression.callee.type !== 'Super' && is_expression_async(expression.callee)) ||
151-
expression.arguments.some((element) => {
152-
if (element.type === 'SpreadElement') {
153-
return is_expression_async(element.argument);
154-
} else {
155-
return is_expression_async(element);
156-
}
157-
})
158-
);
159-
}
160-
case 'ChainExpression': {
161-
return is_expression_async(expression.expression);
162-
}
163-
case 'ConditionalExpression': {
164-
return (
165-
is_expression_async(expression.test) ||
166-
is_expression_async(expression.alternate) ||
167-
is_expression_async(expression.consequent)
168-
);
169-
}
170-
case 'ImportExpression': {
171-
return is_expression_async(expression.source);
172-
}
173-
case 'MemberExpression': {
174-
return (
175-
(expression.object.type !== 'Super' && is_expression_async(expression.object)) ||
176-
(expression.property.type !== 'PrivateIdentifier' &&
177-
is_expression_async(expression.property))
178-
);
179-
}
180-
case 'ObjectPattern':
181-
case 'ObjectExpression': {
182-
return expression.properties.some((property) => {
183-
if (property.type === 'SpreadElement') {
184-
return is_expression_async(property.argument);
185-
} else if (property.type === 'Property') {
186-
return (
187-
(property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
188-
is_expression_async(property.value)
189-
);
190-
}
191-
});
192-
}
193-
case 'RestElement': {
194-
return is_expression_async(expression.argument);
195-
}
196-
case 'SequenceExpression':
197-
case 'TemplateLiteral': {
198-
return expression.expressions.some((subexpression) => is_expression_async(subexpression));
199-
}
200-
case 'TaggedTemplateExpression': {
201-
return is_expression_async(expression.tag) || is_expression_async(expression.quasi);
202-
}
203-
case 'UnaryExpression':
204-
case 'UpdateExpression': {
205-
return is_expression_async(expression.argument);
206-
}
207-
case 'YieldExpression': {
208-
return expression.argument ? is_expression_async(expression.argument) : false;
209-
}
210-
default:
211-
return false;
212-
}
213-
}
214-
215123
/**
216124
* @template {import('./types').ClientTransformState} State
217125
* @param {import('estree').AssignmentExpression} node

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,13 @@ export const javascript_visitors_runes = {
409409
return b.call('$.effect_active');
410410
}
411411

412+
if (rune === '$state.snapshot') {
413+
return b.call(
414+
'$.snapshot',
415+
/** @type {import('estree').Expression} */ (context.visit(node.arguments[0]))
416+
);
417+
}
418+
412419
if (rune === '$effect.root') {
413420
const args = /** @type {import('estree').Expression[]} */ (
414421
node.arguments.map((arg) => context.visit(arg))

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

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,27 +2277,24 @@ export const template_visitors = {
22772277
for (const path of paths) {
22782278
const name = /** @type {import('estree').Identifier} */ (path.node).name;
22792279
const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(name));
2280+
const needs_derived = path.has_default_value; // to ensure that default value is only called once
2281+
const fn = b.thunk(
2282+
/** @type {import('estree').Expression} */ (context.visit(path.expression?.(unwrapped)))
2283+
);
2284+
22802285
declarations.push(
2281-
b.let(
2282-
path.node,
2283-
b.thunk(
2284-
/** @type {import('estree').Expression} */ (
2285-
context.visit(path.expression?.(unwrapped))
2286-
)
2287-
)
2288-
)
2286+
b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn)
2287+
);
2288+
binding.expression = needs_derived ? b.call('$.get', b.id(name)) : b.call(name);
2289+
binding.mutation = create_mutation(
2290+
/** @type {import('estree').Pattern} */ (path.update_expression(unwrapped))
22892291
);
22902292

22912293
// we need to eagerly evaluate the expression in order to hit any
22922294
// 'Cannot access x before initialization' errors
22932295
if (context.state.options.dev) {
2294-
declarations.push(b.stmt(b.call(name)));
2296+
declarations.push(b.stmt(binding.expression));
22952297
}
2296-
2297-
binding.expression = b.call(name);
2298-
binding.mutation = create_mutation(
2299-
/** @type {import('estree').Pattern} */ (path.update_expression(unwrapped))
2300-
);
23012298
}
23022299
}
23032300

@@ -2486,24 +2483,23 @@ export const template_visitors = {
24862483
for (const path of paths) {
24872484
const name = /** @type {import('estree').Identifier} */ (path.node).name;
24882485
const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(name));
2489-
declarations.push(
2490-
b.let(
2491-
path.node,
2492-
b.thunk(
2493-
/** @type {import('estree').Expression} */ (
2494-
context.visit(path.expression?.(b.maybe_call(b.id(arg_alias))))
2495-
)
2496-
)
2486+
const needs_derived = path.has_default_value; // to ensure that default value is only called once
2487+
const fn = b.thunk(
2488+
/** @type {import('estree').Expression} */ (
2489+
context.visit(path.expression?.(b.maybe_call(b.id(arg_alias))))
24972490
)
24982491
);
24992492

2493+
declarations.push(
2494+
b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn)
2495+
);
2496+
binding.expression = needs_derived ? b.call('$.get', b.id(name)) : b.call(name);
2497+
25002498
// we need to eagerly evaluate the expression in order to hit any
25012499
// 'Cannot access x before initialization' errors
25022500
if (context.state.options.dev) {
2503-
declarations.push(b.stmt(b.call(name)));
2501+
declarations.push(b.stmt(binding.expression));
25042502
}
2505-
2506-
binding.expression = b.call(name);
25072503
}
25082504
}
25092505

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
extract_identifiers,
55
extract_paths,
66
is_event_attribute,
7+
is_expression_async,
78
unwrap_optional
89
} from '../../../utils/ast.js';
910
import * as b from '../../../utils/builders.js';
@@ -793,6 +794,10 @@ const javascript_visitors_runes = {
793794
return b.literal(false);
794795
}
795796

797+
if (rune === '$state.snapshot') {
798+
return /** @type {import('estree').Expression} */ (context.visit(node.arguments[0]));
799+
}
800+
796801
if (rune === '$inspect' || rune === '$inspect().with') {
797802
return transform_inspect_rune(node, context);
798803
}
@@ -1191,7 +1196,9 @@ const javascript_visitors_legacy = {
11911196
const name = /** @type {import('estree').Identifier} */ (path.node).name;
11921197
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(name));
11931198
const prop = b.member(b.id('$$props'), b.literal(binding.prop_alias ?? name), true);
1194-
declarations.push(b.declarator(path.node, b.call('$.value_or_fallback', prop, value)));
1199+
declarations.push(
1200+
b.declarator(path.node, b.call('$.value_or_fallback', prop, b.thunk(value)))
1201+
);
11951202
}
11961203
continue;
11971204
}
@@ -1204,13 +1211,15 @@ const javascript_visitors_legacy = {
12041211
b.literal(binding.prop_alias ?? declarator.id.name),
12051212
true
12061213
);
1207-
const init = declarator.init
1208-
? b.call(
1209-
'$.value_or_fallback',
1210-
prop,
1211-
/** @type {import('estree').Expression} */ (visit(declarator.init))
1212-
)
1213-
: prop;
1214+
1215+
/** @type {import('estree').Expression} */
1216+
let init = prop;
1217+
if (declarator.init) {
1218+
const default_value = /** @type {import('estree').Expression} */ (visit(declarator.init));
1219+
init = is_expression_async(default_value)
1220+
? b.await(b.call('$.value_or_fallback_async', prop, b.thunk(default_value, true)))
1221+
: b.call('$.value_or_fallback', prop, b.thunk(default_value));
1222+
}
12141223

12151224
declarations.push(b.declarator(declarator.id, init));
12161225

packages/svelte/src/compiler/phases/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const PassiveEvents = ['wheel', 'touchstart', 'touchmove', 'touchend', 't
3131
export const Runes = /** @type {const} */ ([
3232
'$state',
3333
'$state.frozen',
34+
'$state.snapshot',
3435
'$props',
3536
'$bindable',
3637
'$derived',

0 commit comments

Comments
 (0)