diff --git a/.changeset/smart-fans-crash.md b/.changeset/smart-fans-crash.md
new file mode 100644
index 000000000000..ca38575acba2
--- /dev/null
+++ b/.changeset/smart-fans-crash.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: correctly compile $effect.root in svelte modules
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 b9d6a5bc4da3..581d4dcd03f7 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
@@ -411,6 +411,14 @@ const global_visitors = {
return b.literal(false);
}
+ if (rune === '$effect.root') {
+ const args = /** @type {import('estree').Expression[]} */ (
+ node.arguments.map((arg) => context.visit(arg))
+ );
+ // Just call the function directly
+ return b.call(args[0]);
+ }
+
if (rune === '$state.snapshot') {
return /** @type {import('estree').Expression} */ (context.visit(node.arguments[0]));
}
@@ -571,7 +579,7 @@ const javascript_visitors_runes = {
for (const declarator of node.declarations) {
const init = declarator.init;
const rune = get_rune(init, state.scope);
- if (!rune || rune === '$effect.tracking' || rune === '$inspect') {
+ if (!rune || rune === '$effect.tracking' || rune === '$inspect' || rune === '$effect.root') {
declarations.push(/** @type {import('estree').VariableDeclarator} */ (visit(declarator)));
continue;
}
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root-3/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-root-3/_config.js
new file mode 100644
index 000000000000..fa69197903b2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-root-3/_config.js
@@ -0,0 +1,30 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: '',
+
+ async test({ assert, target, logs }) {
+ const [b1, b2, b3] = target.querySelectorAll('button');
+
+ flushSync(() => {
+ b1.click();
+ b2.click();
+ });
+
+ assert.deepEqual(logs, [0, 1]);
+
+ flushSync(() => {
+ b3.click();
+ });
+
+ assert.deepEqual(logs, [0, 1, 'cleanup 1', 'cleanup 2']);
+
+ flushSync(() => {
+ b1.click();
+ b2.click();
+ });
+
+ assert.deepEqual(logs, [0, 1, 'cleanup 1', 'cleanup 2']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-root-3/main.svelte
new file mode 100644
index 000000000000..fb37dfe47d63
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-root-3/main.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root-3/root.svelte.js b/packages/svelte/tests/runtime-runes/samples/effect-root-3/root.svelte.js
new file mode 100644
index 000000000000..4e05cdf8eff1
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-root-3/root.svelte.js
@@ -0,0 +1,20 @@
+export function with_root(get_x) {
+ const cleanup = $effect.root(() => {
+ $effect(() => {
+ console.log(get_x());
+ });
+
+ const nested_cleanup = $effect.root(() => {
+ return () => {
+ console.log('cleanup 2');
+ };
+ });
+
+ return () => {
+ console.log('cleanup 1');
+ nested_cleanup();
+ };
+ });
+
+ return cleanup;
+}