From 1b675c0264f260b217bf159befb7065f16b62f52 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 15 Jan 2024 15:20:17 +0100
Subject: [PATCH 1/3] fix: transform textarea and contenteditable binding
expressions (#10187)
fixes #10185
---
.changeset/big-cars-serve.md | 5 +++++
.../3-transform/client/visitors/template.js | 10 +++-------
.../3-transform/server/transform-server.js | 17 ++++++++++++-----
.../samples/binding-store/_config.js | 8 ++++++++
.../samples/binding-store/main.svelte | 4 +++-
5 files changed, 31 insertions(+), 13 deletions(-)
create mode 100644 .changeset/big-cars-serve.md
diff --git a/.changeset/big-cars-serve.md b/.changeset/big-cars-serve.md
new file mode 100644
index 0000000000..ada0f61ba9
--- /dev/null
+++ b/.changeset/big-cars-serve.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: transform textarea and contenteditable binding expressions
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
index 6b07a5afd2..3e7cc21403 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
@@ -2595,16 +2595,14 @@ export const template_visitors = {
);
}
- const getter = b.thunk(
- /** @type {import('estree').Expression} */ (context.visit(node.expression))
- );
+ const getter = b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)));
const assignment = b.assignment('=', node.expression, b.id('$$value'));
const setter = b.arrow(
[b.id('$$value')],
serialize_set_binding(
assignment,
context,
- () => /** @type {import('estree').Expression} */ (context.visit(assignment)),
+ () => /** @type {import('estree').Expression} */ (visit(assignment)),
{
skip_proxy_and_freeze: true
}
@@ -2767,9 +2765,7 @@ export const template_visitors = {
group_getter = b.thunk(
b.block([
b.stmt(serialize_attribute_value(value, context)[1]),
- b.return(
- /** @type {import('estree').Expression} */ (context.visit(node.expression))
- )
+ b.return(/** @type {import('estree').Expression} */ (visit(node.expression)))
])
);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
index e068802b7c..49b92191ff 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
@@ -1452,9 +1452,6 @@ const template_visitors = {
context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression)));
}
},
- BindDirective(node, context) {
- // TODO
- },
Component(node, context) {
const state = context.state;
const [dec, id] = serialize_anchor(state);
@@ -1694,9 +1691,19 @@ function serialize_element_attributes(node, context) {
if (binding?.omit_in_ssr) continue;
if (ContentEditableBindings.includes(attribute.name)) {
- content = { escape: false, expression: attribute.expression };
+ content = {
+ escape: false,
+ expression: /** @type {import('estree').Expression} */ (
+ context.visit(attribute.expression)
+ )
+ };
} else if (attribute.name === 'value' && node.name === 'textarea') {
- content = { escape: true, expression: attribute.expression };
+ content = {
+ escape: true,
+ expression: /** @type {import('estree').Expression} */ (
+ context.visit(attribute.expression)
+ )
+ };
} else if (attribute.name === 'group') {
const value_attribute = /** @type {import('#compiler').Attribute | undefined} */ (
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')
diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-store/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-store/_config.js
index 81c83d73de..b8c4ab1f9a 100644
--- a/packages/svelte/tests/runtime-legacy/samples/binding-store/_config.js
+++ b/packages/svelte/tests/runtime-legacy/samples/binding-store/_config.js
@@ -4,11 +4,15 @@ export default test({
html: `
hello world
+
+ world
`,
ssrHtml: `
hello world
+
+ world
`,
async test({ assert, component, target, window }) {
@@ -34,6 +38,8 @@ export default test({
`
hello everybody
+
+ everybody
`
);
@@ -44,6 +50,8 @@ export default test({
`
hello goodbye
+
+ goodbye
`
);
diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-store/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-store/main.svelte
index fd022a34b3..2bcf939af5 100644
--- a/packages/svelte/tests/runtime-legacy/samples/binding-store/main.svelte
+++ b/packages/svelte/tests/runtime-legacy/samples/binding-store/main.svelte
@@ -6,4 +6,6 @@
-hello {$name}
\ No newline at end of file
+hello {$name}
+
+
From 5dce70e6cf4cefaa0744ae828e6fc988229d5957 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Mon, 15 Jan 2024 15:20:31 +0100
Subject: [PATCH 2/3] fix: put more executions after the render effect(s)
(#10186)
snippets and slots should also come after the render effect so that they are executed in order with all the other things (if blocks, actions, etc etc)
---
.../src/compiler/phases/3-transform/client/types.d.ts | 5 ++---
.../compiler/phases/3-transform/client/visitors/template.js | 6 +++---
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
index d94e1f9fe6..85bf8dd278 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
+++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
@@ -29,9 +29,8 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly hoisted: Array;
readonly events: Set;
- /** Stuff that happens before the render effect */
+ /** Stuff that happens before the render effect(s) */
readonly init: Statement[];
-
/** Stuff that happens inside separate render effects (due to call expressions) */
readonly update_effects: Statement[];
/** Stuff that happens inside the render effect */
@@ -42,7 +41,7 @@ export interface ComponentClientTransformState extends ClientTransformState {
/** Used if condition for singular prop is false (see comment above) */
grouped: Statement;
}[];
- /** Stuff that happens after the render effect (bindings, actions) */
+ /** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
readonly after_update: Statement[];
/** The HTML template string */
readonly template: string[];
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
index 3e7cc21403..1e11ac0162 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js
@@ -1798,11 +1798,11 @@ export const template_visitors = {
}
if (is_reactive) {
- context.state.init.push(
+ context.state.after_update.push(
b.stmt(b.call('$.snippet_effect', b.thunk(snippet_function), ...args))
);
} else {
- context.state.init.push(b.stmt(b.call(snippet_function, ...args)));
+ context.state.after_update.push(b.stmt(b.call(snippet_function, ...args)));
}
},
AnimateDirective(node, { state, visit }) {
@@ -2974,7 +2974,7 @@ export const template_visitors = {
: b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true);
const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback);
- context.state.init.push(b.stmt(slot));
+ context.state.after_update.push(b.stmt(slot));
},
SvelteHead(node, context) {
// TODO attributes?
From 86bbc835442a0986133926087fdf446cec7619e6 Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Mon, 15 Jan 2024 22:22:56 +0000
Subject: [PATCH 3/3] fix: ensure unstate() only deeply applies to plain
objects and arrays (#10191)
* fix: ensure unstate() only deeply applies to plain objects and arrays
* tweak
---
.changeset/forty-peaches-unite.md | 5 +++
.../svelte/src/internal/client/proxy/proxy.js | 12 +++---
.../svelte/src/internal/client/runtime.js | 37 +++++++++++++------
packages/svelte/src/internal/client/utils.js | 3 ++
4 files changed, 38 insertions(+), 19 deletions(-)
create mode 100644 .changeset/forty-peaches-unite.md
diff --git a/.changeset/forty-peaches-unite.md b/.changeset/forty-peaches-unite.md
new file mode 100644
index 0000000000..b24993deb2
--- /dev/null
+++ b/.changeset/forty-peaches-unite.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: ensure unstate() only deeply applies to plain objects and arrays
diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js
index ae993267d6..d0bb285e10 100644
--- a/packages/svelte/src/internal/client/proxy/proxy.js
+++ b/packages/svelte/src/internal/client/proxy/proxy.js
@@ -11,11 +11,15 @@ import {
batch_inspect
} from '../runtime.js';
import {
+ array_prototype,
define_property,
get_descriptor,
get_descriptors,
+ get_prototype_of,
is_array,
- object_keys
+ is_frozen,
+ object_keys,
+ object_prototype
} from '../utils.js';
/** @typedef {{ s: Map>; v: import('../types.js').SourceSignal; a: boolean, i: boolean, p: StateObject }} Metadata */
@@ -23,12 +27,6 @@ import {
export const STATE_SYMBOL = Symbol('$state');
export const READONLY_SYMBOL = Symbol('readonly');
-
-const object_prototype = Object.prototype;
-const array_prototype = Array.prototype;
-const get_prototype_of = Object.getPrototypeOf;
-const is_frozen = Object.isFrozen;
-
/**
* @template {StateObject} T
* @param {T} value
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 126692b1a1..97804e11ce 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -1,7 +1,16 @@
import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC, run_all } from '../common.js';
-import { get_descriptor, get_descriptors, is_array, is_frozen, object_freeze } from './utils.js';
+import {
+ array_prototype,
+ get_descriptor,
+ get_descriptors,
+ get_prototype_of,
+ is_array,
+ is_frozen,
+ object_freeze,
+ object_prototype
+} from './utils.js';
import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
@@ -1975,19 +1984,23 @@ function deep_unstate(value, visited = new Map()) {
visited.set(value, unstated);
return unstated;
}
-
- let contains_unstated = false;
- /** @type {any} */
- const nested_unstated = Array.isArray(value) ? [] : {};
- for (let key in value) {
- const result = deep_unstate(value[key], visited);
- nested_unstated[key] = result;
- if (result !== value[key]) {
- contains_unstated = true;
+ const prototype = get_prototype_of(value);
+ // Only deeply unstate plain objects and arrays
+ if (prototype === object_prototype || prototype === array_prototype) {
+ let contains_unstated = false;
+ /** @type {any} */
+ const nested_unstated = Array.isArray(value) ? [] : {};
+ for (let key in value) {
+ const result = deep_unstate(value[key], visited);
+ nested_unstated[key] = result;
+ if (result !== value[key]) {
+ contains_unstated = true;
+ }
}
+ visited.set(value, contains_unstated ? nested_unstated : value);
+ } else {
+ visited.set(value, value);
}
-
- visited.set(value, contains_unstated ? nested_unstated : value);
}
return visited.get(value) ?? value;
diff --git a/packages/svelte/src/internal/client/utils.js b/packages/svelte/src/internal/client/utils.js
index 7c1b01515e..1da6f2fb39 100644
--- a/packages/svelte/src/internal/client/utils.js
+++ b/packages/svelte/src/internal/client/utils.js
@@ -10,6 +10,9 @@ export var object_freeze = Object.freeze;
export var define_property = Object.defineProperty;
export var get_descriptor = Object.getOwnPropertyDescriptor;
export var get_descriptors = Object.getOwnPropertyDescriptors;
+export var object_prototype = Object.prototype;
+export var array_prototype = Array.prototype;
+export var get_prototype_of = Object.getPrototypeOf;
/**
* @param {any} thing