Skip to content

Commit ad20f48

Browse files
authored
Merge branch 'main' into tweak-snapshot-example
2 parents ba1e2fa + b27113d commit ad20f48

File tree

44 files changed

+504
-224
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+504
-224
lines changed

.changeset/hip-months-breathe.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: show correct errors for invalid runes in `.svelte.js` files

.changeset/perfect-actors-bake.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: use structuredClone inside `$state.snapshot`

.changeset/pre.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@
252252
"heavy-ears-rule",
253253
"hip-balloons-begin",
254254
"hip-garlics-tap",
255+
"hip-months-breathe",
255256
"hip-pumpkins-boil",
256257
"honest-buses-add",
257258
"honest-dragons-turn",
@@ -408,6 +409,7 @@
408409
"orange-masks-exercise",
409410
"orange-yaks-protect",
410411
"orange-zoos-heal",
412+
"perfect-actors-bake",
411413
"pink-bikes-agree",
412414
"pink-goats-promise",
413415
"pink-mayflies-tie",

documentation/docs/03-runes/01-state.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ This can improve performance with large arrays and objects that you weren't plan
9292

9393
In development mode, the argument to `$state.frozen` will be shallowly frozen with `Object.freeze()`, to make it obvious if you accidentally mutate it.
9494

95-
> Objects and arrays passed to `$state.frozen` will have a `Symbol` property added to them to signal to Svelte that they are frozen. If you don't want this, pass in a clone of the object or array instead.
95+
> Objects and arrays passed to `$state.frozen` will have a `Symbol` property added to them to signal to Svelte that they are frozen. If you don't want this, pass in a clone of the object or array instead. The argument cannot be an existing state proxy created with `$state(...)`.
9696
9797
## `$state.snapshot`
9898

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.184
4+
5+
### Patch Changes
6+
7+
- fix: show correct errors for invalid runes in `.svelte.js` files ([#12432](https://github.com/sveltejs/svelte/pull/12432))
8+
9+
- breaking: use structuredClone inside `$state.snapshot` ([#12413](https://github.com/sveltejs/svelte/pull/12413))
10+
311
## 5.0.0-next.183
412

513
### Patch Changes

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060

6161
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
6262
63+
## state_frozen_invalid_argument
64+
65+
> The argument to `$state.frozen(...)` cannot be an object created with `$state(...)`. You should create a copy of it first, for example with `$state.snapshot`
66+
6367
## state_prototype_fixed
6468

6569
> Cannot set prototype of `$state` object

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.183",
5+
"version": "5.0.0-next.184",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/ambient.d.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,75 @@ declare function $state<T>(initial: T): T;
3232
declare function $state<T>(): T | undefined;
3333

3434
declare namespace $state {
35+
type Primitive = string | number | boolean | null | undefined;
36+
37+
type TypedArray =
38+
| Int8Array
39+
| Uint8Array
40+
| Uint8ClampedArray
41+
| Int16Array
42+
| Uint16Array
43+
| Int32Array
44+
| Uint32Array
45+
| Float32Array
46+
| Float64Array
47+
| BigInt64Array
48+
| BigUint64Array;
49+
50+
/** The things that `structuredClone` can handle — https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm */
51+
export type Cloneable =
52+
| ArrayBuffer
53+
| DataView
54+
| Date
55+
| Error
56+
| Map<any, any>
57+
| RegExp
58+
| Set<any>
59+
| TypedArray
60+
// web APIs
61+
| Blob
62+
| CryptoKey
63+
| DOMException
64+
| DOMMatrix
65+
| DOMMatrixReadOnly
66+
| DOMPoint
67+
| DOMPointReadOnly
68+
| DOMQuad
69+
| DOMRect
70+
| DOMRectReadOnly
71+
| File
72+
| FileList
73+
| FileSystemDirectoryHandle
74+
| FileSystemFileHandle
75+
| FileSystemHandle
76+
| ImageBitmap
77+
| ImageData
78+
| RTCCertificate
79+
| VideoFrame;
80+
81+
/** Turn `SvelteDate`, `SvelteMap` and `SvelteSet` into their non-reactive counterparts. (`URL` is uncloneable.) */
82+
type NonReactive<T> = T extends Date
83+
? Date
84+
: T extends Map<infer K, infer V>
85+
? Map<K, V>
86+
: T extends Set<infer K>
87+
? Set<K>
88+
: T;
89+
90+
type Snapshot<T> = T extends Primitive
91+
? T
92+
: T extends Cloneable
93+
? NonReactive<T>
94+
: T extends { toJSON(): infer R }
95+
? R
96+
: T extends Array<infer U>
97+
? Array<Snapshot<U>>
98+
: T extends object
99+
? T extends { [key: string]: any }
100+
? { [K in keyof T]: Snapshot<T[K]> }
101+
: never
102+
: never;
103+
35104
/**
36105
* Declares reactive read-only state that is shallowly immutable.
37106
*
@@ -75,7 +144,7 @@ declare namespace $state {
75144
*
76145
* @param state The value to snapshot
77146
*/
78-
export function snapshot<T>(state: T): T;
147+
export function snapshot<T>(state: T): Snapshot<T>;
79148

80149
/**
81150
* Compare two values, one or both of which is a reactive `$state(...)` proxy.

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

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,41 @@ export const validation_runes_js = {
10431043
if (node.callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) {
10441044
w.perf_avoid_inline_class(node);
10451045
}
1046+
},
1047+
Identifier(node, { path, state }) {
1048+
let i = path.length;
1049+
let parent = /** @type {import('estree').Expression} */ (path[--i]);
1050+
1051+
if (
1052+
Runes.includes(/** @type {Runes[number]} */ (node.name)) &&
1053+
is_reference(node, parent) &&
1054+
state.scope.get(node.name) === null &&
1055+
state.scope.get(node.name.slice(1)) === null
1056+
) {
1057+
/** @type {import('estree').Expression} */
1058+
let current = node;
1059+
let name = node.name;
1060+
1061+
while (parent.type === 'MemberExpression') {
1062+
if (parent.computed) e.rune_invalid_computed_property(parent);
1063+
name += `.${/** @type {import('estree').Identifier} */ (parent.property).name}`;
1064+
1065+
current = parent;
1066+
parent = /** @type {import('estree').Expression} */ (path[--i]);
1067+
1068+
if (!Runes.includes(/** @type {Runes[number]} */ (name))) {
1069+
if (name === '$effect.active') {
1070+
e.rune_renamed(parent, '$effect.active', '$effect.tracking');
1071+
}
1072+
1073+
e.rune_invalid_name(parent, name);
1074+
}
1075+
}
1076+
1077+
if (parent.type !== 'CallExpression') {
1078+
e.rune_missing_parentheses(current);
1079+
}
1080+
}
10461081
}
10471082
};
10481083

@@ -1153,41 +1188,6 @@ export const validation_runes = merge(validation, a11y_validators, {
11531188
e.import_svelte_internal_forbidden(node);
11541189
}
11551190
},
1156-
Identifier(node, { path, state }) {
1157-
let i = path.length;
1158-
let parent = /** @type {import('estree').Expression} */ (path[--i]);
1159-
1160-
if (
1161-
Runes.includes(/** @type {Runes[number]} */ (node.name)) &&
1162-
is_reference(node, parent) &&
1163-
state.scope.get(node.name) === null &&
1164-
state.scope.get(node.name.slice(1)) === null
1165-
) {
1166-
/** @type {import('estree').Expression} */
1167-
let current = node;
1168-
let name = node.name;
1169-
1170-
while (parent.type === 'MemberExpression') {
1171-
if (parent.computed) e.rune_invalid_computed_property(parent);
1172-
name += `.${/** @type {import('estree').Identifier} */ (parent.property).name}`;
1173-
1174-
current = parent;
1175-
parent = /** @type {import('estree').Expression} */ (path[--i]);
1176-
1177-
if (!Runes.includes(/** @type {Runes[number]} */ (name))) {
1178-
if (name === '$effect.active') {
1179-
e.rune_renamed(parent, '$effect.active', '$effect.tracking');
1180-
}
1181-
1182-
e.rune_invalid_name(parent, name);
1183-
}
1184-
}
1185-
1186-
if (parent.type !== 'CallExpression') {
1187-
e.rune_missing_parentheses(current);
1188-
}
1189-
}
1190-
},
11911191
LabeledStatement(node, { path }) {
11921192
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
11931193
e.legacy_reactive_statement_invalid(node);
@@ -1368,5 +1368,6 @@ export const validation_runes = merge(validation, a11y_validators, {
13681368
// TODO this is a code smell. need to refactor this stuff
13691369
ClassBody: validation_runes_js.ClassBody,
13701370
ClassDeclaration: validation_runes_js.ClassDeclaration,
1371+
Identifier: validation_runes_js.Identifier,
13711372
NewExpression: validation_runes_js.NewExpression
13721373
});

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,10 @@ const global_visitors = {
423423
}
424424

425425
if (rune === '$state.snapshot') {
426-
return /** @type {import('estree').Expression} */ (context.visit(node.arguments[0]));
426+
return b.call(
427+
'$.snapshot',
428+
/** @type {import('estree').Expression} */ (context.visit(node.arguments[0]))
429+
);
427430
}
428431

429432
if (rune === '$state.is') {

packages/svelte/src/index-client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { current_component_context, flush_sync, untrack } from './internal/client/runtime.js';
2-
import { is_array } from './internal/client/utils.js';
2+
import { is_array } from './internal/shared/utils.js';
33
import { user_effect } from './internal/client/index.js';
44
import * as e from './internal/client/errors.js';
55
import { lifecycle_outside_component } from './internal/shared/errors.js';
Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { snapshot } from '../proxy.js';
1+
import { snapshot } from '../../shared/clone.js';
22
import { inspect_effect, validate_effect } from '../reactivity/effects.js';
3-
import { array_prototype, get_prototype_of, object_prototype } from '../utils.js';
43

54
/**
65
* @param {() => any[]} get_value
@@ -13,47 +12,7 @@ export function inspect(get_value, inspector = console.log) {
1312
let initial = true;
1413

1514
inspect_effect(() => {
16-
inspector(initial ? 'init' : 'update', ...deep_snapshot(get_value()));
15+
inspector(initial ? 'init' : 'update', ...snapshot(get_value()));
1716
initial = false;
1817
});
1918
}
20-
21-
/**
22-
* Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
23-
* @param {any} value
24-
* @param {Map<any, any>} visited
25-
* @returns {any}
26-
*/
27-
function deep_snapshot(value, visited = new Map()) {
28-
if (typeof value === 'object' && value !== null && !visited.has(value)) {
29-
const unstated = snapshot(value);
30-
31-
if (unstated !== value) {
32-
visited.set(value, unstated);
33-
return unstated;
34-
}
35-
36-
const prototype = get_prototype_of(value);
37-
38-
// Only deeply snapshot plain objects and arrays
39-
if (prototype === object_prototype || prototype === array_prototype) {
40-
let contains_unstated = false;
41-
/** @type {any} */
42-
const nested_unstated = Array.isArray(value) ? [] : {};
43-
44-
for (let key in value) {
45-
const result = deep_snapshot(value[key], visited);
46-
nested_unstated[key] = result;
47-
if (result !== value[key]) {
48-
contains_unstated = true;
49-
}
50-
}
51-
52-
visited.set(value, contains_unstated ? nested_unstated : value);
53-
} else {
54-
visited.set(value, value);
55-
}
56-
}
57-
58-
return visited.get(value) ?? value;
59-
}

packages/svelte/src/internal/client/dev/ownership.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { STATE_SYMBOL } from '../constants.js';
55
import { render_effect, user_pre_effect } from '../reactivity/effects.js';
66
import { dev_current_component_function } from '../runtime.js';
7-
import { get_prototype_of } from '../utils.js';
7+
import { get_prototype_of } from '../../shared/utils.js';
88
import * as w from '../warnings.js';
99

1010
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
resume_effect
2929
} from '../../reactivity/effects.js';
3030
import { source, mutable_source, set } from '../../reactivity/sources.js';
31-
import { is_array, is_frozen } from '../../utils.js';
31+
import { is_array, is_frozen } from '../../../shared/utils.js';
3232
import { INERT, STATE_FROZEN_SYMBOL, STATE_SYMBOL } from '../../constants.js';
3333
import { queue_micro_task } from '../task.js';
3434
import { current_effect } from '../../runtime.js';

packages/svelte/src/internal/client/dom/css.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function append_styles(anchor, css) {
2020

2121
var target = /** @type {ShadowRoot} */ (root).host
2222
? /** @type {ShadowRoot} */ (root)
23-
: /** @type {Document} */ (root).head;
23+
: /** @type {Document} */ (root).head ?? /** @type {Document} */ (root.ownerDocument).head;
2424

2525
if (!target.querySelector('#' + css.hash)) {
2626
const style = document.createElement('style');

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DEV } from 'esm-env';
22
import { hydrating } from '../hydration.js';
3-
import { get_descriptors, get_prototype_of } from '../../utils.js';
3+
import { get_descriptors, get_prototype_of } from '../../../shared/utils.js';
44
import {
55
AttributeAliases,
66
DelegatedEvents,

packages/svelte/src/internal/client/dom/elements/bindings/props.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { teardown } from '../../../reactivity/effects.js';
2-
import { get_descriptor } from '../../../utils.js';
2+
import { get_descriptor } from '../../../../shared/utils.js';
33

44
/**
55
* Makes an `export`ed (non-prop) variable available on the `$$props` object

packages/svelte/src/internal/client/dom/elements/custom-element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createClassComponent } from '../../../../legacy/legacy-client.js';
22
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
33
import { append } from '../template.js';
4-
import { define_property, object_keys } from '../../utils.js';
4+
import { define_property, object_keys } from '../../../shared/utils.js';
55

66
/**
77
* @typedef {Object} CustomElementPropDefinition

packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { teardown } from '../../reactivity/effects.js';
2-
import { define_property, is_array } from '../../utils.js';
2+
import { define_property, is_array } from '../../../shared/utils.js';
33
import { hydrating } from '../hydration.js';
44
import { queue_micro_task } from '../task.js';
55

packages/svelte/src/internal/client/dom/elements/transitions.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { noop } from '../../../shared/utils.js';
1+
import { noop, is_function } from '../../../shared/utils.js';
22
import { effect } from '../../reactivity/effects.js';
33
import { current_effect, untrack } from '../../runtime.js';
44
import { raf } from '../../timing.js';
55
import { loop } from '../../loop.js';
66
import { should_intro } from '../../render.js';
7-
import { is_function } from '../../utils.js';
87
import { current_each_item } from '../blocks/each.js';
98
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
109
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js';

0 commit comments

Comments
 (0)