Skip to content

Commit 17e6c4f

Browse files
trueadmdummdidumm
andauthored
fix: address runtime effect issues (#9417)
* Fix runtime effect issues * Prettier * Add changeset * Fix operations * Update .changeset/khaki-mails-draw.md * more tweaks * more tweaks --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
1 parent 8798f3b commit 17e6c4f

File tree

12 files changed

+133
-20
lines changed

12 files changed

+133
-20
lines changed

.changeset/khaki-mails-draw.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: tighten up signals implementation

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ export const function_visitor = (node, context) => {
251251
const in_constructor = parent.type === 'MethodDefinition' && parent.kind === 'constructor';
252252

253253
state = { ...context.state, in_constructor };
254+
} else {
255+
state = { ...context.state, in_constructor: false };
254256
}
255257

256258
if (metadata?.hoistable === true) {

packages/svelte/src/internal/client/operations.js

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,19 @@ const has_browser_globals = typeof window !== 'undefined';
88
// than megamorphic.
99
const node_prototype = /** @type {Node} */ (has_browser_globals ? Node.prototype : {});
1010
const element_prototype = /** @type {Element} */ (has_browser_globals ? Element.prototype : {});
11-
const event_target_prototype = /** @type {EventTarget} */ (
12-
has_browser_globals ? EventTarget.prototype : {}
13-
);
11+
const text_prototype = /** @type {Text} */ (has_browser_globals ? Text.prototype : {});
1412
const map_prototype = Map.prototype;
1513
const append_child_method = node_prototype.appendChild;
1614
const clone_node_method = node_prototype.cloneNode;
1715
const map_set_method = map_prototype.set;
1816
const map_get_method = map_prototype.get;
1917
const map_delete_method = map_prototype.delete;
20-
// @ts-expect-error improve perf of expando on DOM nodes for events
21-
event_target_prototype.__click = undefined;
22-
// @ts-expect-error improve perf of expando on DOM textValue updates
23-
event_target_prototype.__nodeValue = ' ';
18+
// @ts-expect-error improve perf of expando on DOM events
19+
element_prototype.__click = undefined;
20+
// @ts-expect-error improve perf of expando on DOM text updates
21+
text_prototype.__nodeValue = ' ';
2422
// @ts-expect-error improve perf of expando on DOM className updates
25-
event_target_prototype.__className = '';
23+
element_prototype.__className = '';
2624

2725
const first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
2826
// @ts-ignore
@@ -162,11 +160,10 @@ export function set_class_name(node, class_name) {
162160
/**
163161
* @template {Node} N
164162
* @param {N} node
165-
* @param {string} text
166163
* @returns {void}
167164
*/
168-
export function text_content(node, text) {
169-
text_content_set.call(node, text);
165+
export function clear_text_content(node) {
166+
text_content_set.call(node, '');
170167
}
171168

172169
/** @param {string} name */

packages/svelte/src/internal/client/reconciler.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { append_child, map_get, map_set, text_content } from './operations.js';
1+
import { append_child, map_get, map_set, clear_text_content } from './operations.js';
22
import {
33
current_hydration_fragment,
44
get_hydration_fragment,
@@ -198,7 +198,7 @@ export function reconcile_indexed_array(
198198
b_blocks = [];
199199
// Remove old blocks
200200
if (is_controlled && a !== 0) {
201-
text_content(dom, '');
201+
clear_text_content(dom);
202202
}
203203
while (index < length) {
204204
block = a_blocks[index++];
@@ -295,7 +295,7 @@ export function reconcile_tracked_array(
295295
b_blocks = [];
296296
// Remove old blocks
297297
if (is_controlled && a !== 0) {
298-
text_content(dom, '');
298+
clear_text_content(dom);
299299
}
300300
while (a > 0) {
301301
block = a_blocks[--a];

packages/svelte/src/internal/client/render.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,15 +1270,15 @@ function handle_event_propagation(root_element, event) {
12701270
}
12711271

12721272
// composedPath contains list of nodes the event has propagated through.
1273-
// We check __handled_event_at to skip all nodes below it in case this is a
1274-
// parent of the __handled_event_at node, which indicates that there's nested
1273+
// We check __root to skip all nodes below it in case this is a
1274+
// parent of the __root node, which indicates that there's nested
12751275
// mounted apps. In this case we don't want to trigger events multiple times.
12761276
// We're deliberately not skipping if the index is the same or higher, because
12771277
// someone could create an event programmatically and emit it multiple times,
12781278
// in which case we want to handle the whole propagation chain properly each time.
12791279
let path_idx = 0;
12801280
// @ts-expect-error is added below
1281-
const handled_at = event.__handled_event_at;
1281+
const handled_at = event.__root;
12821282
if (handled_at) {
12831283
const at_idx = path.indexOf(handled_at);
12841284
if (at_idx < path.indexOf(root_element)) {
@@ -1317,7 +1317,7 @@ function handle_event_propagation(root_element, event) {
13171317
}
13181318

13191319
// @ts-expect-error is used above
1320-
event.__handled_event_at = root_element;
1320+
event.__root = root_element;
13211321
}
13221322

13231323
/**

packages/svelte/src/internal/client/runtime.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,13 @@ function destroy_references(signal) {
343343
if (references !== null) {
344344
let i;
345345
for (i = 0; i < references.length; i++) {
346-
destroy_signal(references[i]);
346+
const reference = references[i];
347+
if ((reference.flags & IS_EFFECT) !== 0) {
348+
destroy_signal(reference);
349+
} else {
350+
remove_consumer(reference, 0, true);
351+
reference.dependencies = null;
352+
}
347353
}
348354
}
349355
}
@@ -710,7 +716,7 @@ export function exposable(fn) {
710716
export function get(signal) {
711717
const flags = signal.flags;
712718
if ((flags & DESTROYED) !== 0) {
713-
return /** @type {V} */ (UNINITIALIZED);
719+
return signal.value;
714720
}
715721

716722
if (is_signal_exposed && current_should_capture_signal) {
@@ -1156,6 +1162,11 @@ export function managed_pre_effect(init, sync) {
11561162
* @returns {import('./types.js').EffectSignal}
11571163
*/
11581164
export function pre_effect(init) {
1165+
if (current_effect === null) {
1166+
throw new Error(
1167+
'The Svelte $effect.pre rune can only be used during component initialisation.'
1168+
);
1169+
}
11591170
const sync = current_effect !== null && (current_effect.flags & RENDER_EFFECT) !== 0;
11601171
return internal_create_effect(
11611172
PRE_EFFECT,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>10</button>`,
6+
ssrHtml: `<button>0</button>`,
7+
8+
async test({ assert, target }) {
9+
flushSync();
10+
11+
assert.htmlEqual(target.innerHTML, `<button>10</button>`);
12+
}
13+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
class Counter {
3+
count = $state(0);
4+
5+
constructor() {
6+
$effect(() => {
7+
this.count = 10;
8+
});
9+
}
10+
}
11+
const counter = new Counter();
12+
</script>
13+
14+
<button on:click={() => counter.count++}>{counter.count}</button>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>10</button>`,
6+
ssrHtml: `<button>0</button>`,
7+
8+
async test({ assert, target }) {
9+
flushSync();
10+
11+
assert.htmlEqual(target.innerHTML, `<button>10</button>`);
12+
}
13+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script>
2+
class Counter {
3+
#count = $state(0);
4+
5+
constructor() {
6+
$effect(() => {
7+
this.#count = 10;
8+
});
9+
}
10+
11+
getCount() {
12+
return this.#count;
13+
}
14+
15+
increment() {
16+
this.#count++;
17+
}
18+
}
19+
const counter = new Counter();
20+
</script>
21+
22+
<button on:click={() => counter.increment()}>{counter.getCount()}</button>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
get props() {
6+
return { log: [] };
7+
},
8+
9+
async test({ assert, target, component }) {
10+
const [b1] = target.querySelectorAll('button');
11+
flushSync(() => {
12+
b1.click();
13+
});
14+
flushSync(() => {
15+
b1.click();
16+
});
17+
assert.deepEqual(component.log, ['init 0', 'cleanup 2', 'init 2', 'cleanup 4', 'init 4']);
18+
}
19+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
const {log} = $props();
3+
4+
let count = $state(0);
5+
6+
$effect(() => {
7+
let double = $derived(count * 2)
8+
9+
log.push('init ' + double);
10+
11+
return () => {
12+
log.push('cleanup ' + double);
13+
};
14+
})
15+
</script>
16+
17+
<button on:click={() => count++ }>Click</button>

0 commit comments

Comments
 (0)