diff --git a/.changeset/eighty-hairs-remain.md b/.changeset/eighty-hairs-remain.md new file mode 100644 index 000000000000..55cd8e1fcd74 --- /dev/null +++ b/.changeset/eighty-hairs-remain.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add $effect.active rune diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 6a301726b106..b4a69e5a24c9 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -109,6 +109,7 @@ export function CallExpression(node, context) { break; + case '$effect.active': case '$effect.tracking': if (node.arguments.length !== 0) { e.rune_invalid_arguments(node, rune); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index 79dccd5a7cf5..4de72a4217c1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -53,10 +53,6 @@ export function Identifier(node, context) { parent = /** @type {Expression} */ (context.path[--i]); if (!is_rune(name)) { - if (name === '$effect.active') { - e.rune_renamed(parent, '$effect.active', '$effect.tracking'); - } - if (name === '$state.frozen') { e.rune_renamed(parent, '$state.frozen', '$state.raw'); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 7a3057451aa1..9f1e355703cb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -17,6 +17,9 @@ export function CallExpression(node, context) { case '$effect.tracking': return b.call('$.effect_tracking'); + case '$effect.active': + return b.call('$.effect_active'); + case '$state.snapshot': return b.call( '$.snapshot', diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index afb90bbec7f9..dea8924e4730 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -25,6 +25,7 @@ export function VariableDeclaration(node, context) { if ( !rune || rune === '$effect.tracking' || + rune === '$effect.active' || rune === '$effect.root' || rune === '$inspect' || rune === '$inspect.trace' || diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index 386c6b6ff393..ee2b18c889c3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -16,7 +16,7 @@ export function CallExpression(node, context) { return b.id('undefined'); } - if (rune === '$effect.tracking') { + if (rune === '$effect.tracking' || rune === '$effect.active') { return b.literal(false); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js index 31de811ac76f..b3e875b7d084 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js @@ -19,7 +19,13 @@ export function VariableDeclaration(node, context) { for (const declarator of node.declarations) { const init = declarator.init; const rune = get_rune(init, context.state.scope); - if (!rune || rune === '$effect.tracking' || rune === '$inspect' || rune === '$effect.root') { + if ( + !rune || + rune === '$effect.tracking' || + rune === '$effect.active' || + rune === '$inspect' || + rune === '$effect.root' + ) { declarations.push(/** @type {VariableDeclarator} */ (context.visit(declarator))); continue; } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index f22c33babc52..8113bfd0dd18 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -100,6 +100,7 @@ export { export { derived, derived_safe_equal } from './reactivity/deriveds.js'; export { effect_tracking, + effect_active, effect_root, legacy_pre_effect, legacy_pre_effect_reset, diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index bf890627f7e0..7d393ae1b587 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -173,6 +173,19 @@ export function effect_tracking() { return !skip_reaction; } +/** + * Internal representation of `$effect.active()` + * @returns {boolean} + */ +export function effect_active() { + if (is_destroying_effect) { + return false; + } + return ( + active_effect !== null || (active_reaction !== null && (active_reaction.f & UNOWNED) === 0) + ); +} + /** * @param {() => void} fn */ diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index e42721b4f4b4..b5e0c0496e24 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -428,6 +428,7 @@ const RUNES = /** @type {const} */ ([ '$effect', '$effect.pre', '$effect.tracking', + '$effect.active', '$effect.root', '$inspect', '$inspect().with', diff --git a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js deleted file mode 100644 index e12e4046f36d..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '../../test'; - -export default test({ - error: { - code: 'rune_renamed', - message: '`$effect.active` is now `$effect.tracking`' - } -}); diff --git a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js deleted file mode 100644 index c33c104aac31..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js +++ /dev/null @@ -1 +0,0 @@ -$effect.active(); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js new file mode 100644 index 000000000000..8629829c6451 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js @@ -0,0 +1,19 @@ +import { test } from '../../test'; + +export default test({ + ssrHtml: ` +

false

+

false

+

false

+

false

+

false

+ `, + + html: ` +

false

+

true

+

true

+

true

+

true

+ ` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte new file mode 100644 index 000000000000..bf488752db67 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte @@ -0,0 +1,19 @@ + + + + +

{mod}

+

{foo}

+

{bar}

+

{(bar, $effect.active())}

+

{untrack(() => (bar, $effect.active()))}