Closed eddiemcconkie closed 7 months ago
I suspect that untrack
mainly exists for $effect
, so an entire block of logic, regardless of how many variable references it contains, can be excluded.
$effect(() => {
// <reactive>
untrack(() => {
// <unreactive>
});
});
Yeah that makes sense. I guess I didn't consider that untrack
can be used to do more than just return a single untracked value
I'm going to reopen this to see how others feel about this. We're not entirely sure yet ourselves if we want to make it a rune or not.
Without expressing an opinion on this particular issue, I will say that our current philosophy of only making things runes if they absolutely have to be perhaps can be difficult to approach from a user-perspective because it requires a very high degree of knowledge to know whether something would be a rune or not.
From the preview site docs:
These are introduced as functions rather than runes because they are used directly and the compiler does not need to touch them to make them function as it does with runes. However, these functions may still use Svelte internals.
I think that should not define whether something should be a rune or not. Runes is more of a marketing term and it should be so. It makes more sense to me to make it a rune as it purely exists for usage inside $effect
and $derived
runes, and it is part of what defines the Svelte 5 reactivity system.
Another argument of making it a rune: By the logic of "only make things a rune that absolutely have to", $effect
also wouldn't be a rune, because you could define it as a function that is a noop in SSR and so bundlers would dead-code-eleminate everything inside.
Hello,
Just my two-cents.
IMHO the rune is more logical (and more concise) when accessing a state/prop.
$state
, $derived
and $props
runes allow us to declare reactive states/props.
It's seem logical that another runes allows us to temporarily deactivate this reactivity on a specific state/prop.
And, we should use two syntax :
$untrack(count)
should be equivalent to untrack(() => count)
, with side-effect check.$untrack(() => { /* untracked code */ })
for any untracked code, possibly with side-effects.Also, not sure if I should post here or create another issue, but I think that untrack should handle proxified object/array. If the return value is a proxified object/array, it should be automatically untracked.
For example image this :
let array = $state([ ... ]);
let value = $state('val');
$effect(() => {
const a = untrack(() => array);
console.log("array length: " + a.length);
console.log("includes value: " + a.includes(value));
});
Here array
is still tracked because a.length
and a.includes(value)
are proxified.
To fix that, we must wrap all uses of array
with untrack()
, which is less readable :
$effect(() => {
console.log("array length: " + untrack(() => array.length));
console.log("includes value: " + untrack(() => array.includes(value)));
value; // needed to track value, because of untrack
});
I think that this line untrack(() => array)
should handle this case, by returning a proxified array/object that use untrack internally.
A variation of $effect
with explicit dependencies could cover most of the use cases that untrack
is used for — see #9248.
Instead of making untrack
a rune, if you're going to add a rune, why not just add an explicit effect (a.k.a. $watch
) instead?
With automatic dependencies, I don't see any interest of explicit dependencies. It's a regression.
I don't want explicit dependencies, just a simplier way to untrack some states/props...
our current philosophy of only making things runes if they absolutely have to be perhaps can be difficult to approach from a user-perspective because it requires a very high degree of knowledge to know whether something would be a rune or not.
I hope the philosophy has since shifted further away from this towards creating a more consistent developer experience. $untrack(value)
seems more Svelte-like to me than untrack(() => value)
.
A variation of
$effect
with explicit dependencies could cover most of the use cases thatuntrack
is used for — see #9248.Instead of making
untrack
a rune, if you're going to add a rune, why not just add an explicit effect (a.k.a.$watch
) instead?
It happened to me a couple times that I needed to manually control which variables triggered reactivity, and in all those cases it was easier for me to specify which variables to track, and not which ones to untrack. I just created a function receiving only the variables I want to track, even though inside the function I might access other top-level (and thus reactive) variables.
It seems to me like that would be the most common scenario. So I think something like https://github.com/sveltejs/svelte/issues/9248 would be very useful indeed.
One major benefit of a rune
is auto imports for that feature.
For me the problem with untrack
as a function and not a rune
is that you have to import
a first-class library feature, where as almost every other userland feature you would use in a component for example is a rune
without any import
.
In this way import
can be reserved for exposing rare internal library or third party features, and so instead of drawing the line at "does the compiler touch this" it can be rather "is this a first-class framework feature that is inconsistent and annoying to have to import".
Tbf onMount, on destroy, before/after/onNavigate are all first-class framework features that have to be imported
@eddiemcconkie onMount
is deprecated in favor of $effect
in 5, but I'm not sure about onNavigate
.
As a user I would want to have $navigate
act like yet another side-effectual "rune" like $effect
.
The point is it interrupts the flow in userland to have to think about which functionality needs to be imported, and that it is really only beneficial to the library author to know the distinction between compiler-only functions.
Something I am unsure of, is if there are people using svelte
without the compilation step. That is the only reason i can think to maintain the ability to import the non-compiler functions.
Otherwise since you've already started down this path with $:
, $count
, on:click
, and now runes, might as well go all the way. If someone is opinionated against the compiler magic and wants to see everything that is used imported at the top of their script tag they probably shouldn't be using svelte to begin with, right?
Does anyone remember classes autoload in PHP? I actually find it much nicer when you have a subset of known functionality that you want to tap into to not have to junk up files in the same project space with includes...
If we slightly modify that definition:
https://svelte-5-preview.vercel.app/docs/runes
Runes are function-like symbols
that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language.
Then for a rune
you can ask:
Looking at the preview docs, before/afterUpdate are getting deprecated. I believe onMount is sticking around. You can use an effect to do the same thing as onMount, but you'd need to untrack any reactive state manually if you just want to run something once when the component mounts, so I think onMount is still more convenient in some cases. onNavigate is useful for view transitions.
The point is it interrupts the flow in userland to have to think about which functionality needs to be imported
Honestly this isn't much of a concern when your text editor does auto imports. Svelte tries to rely on web standards and native language features as much as possible and sprinkle in a little magic when needed, so it makes sense to import most of Svelte's features since it's just regular JavaScript code, and only make runes globally accessible since they're not JavaScript but part of the Svelte language.
Originally I thought untrack would be better as a rune, but I do think what the Svelte team said about only making things runes when necessary makes a lot of sense and I can get on board with that.
I do think what the Svelte team said about only making things runes when necessary makes a lot of sense and I can get on board with that.
@eddiemcconkie I respect that, but whole-heartedly disagree. You mentioning the IDE auto-import is rather ironic considering how that's a poor man's version of what I think would be the more robust option.
Svelte exports more than just lifecycle functions. There are also things like stores or transition functions. Explicit imports help avoid naming collisions. It probably also makes it much easier for the Svelte team since they can just write and export plain JavaScript and not have to worry about making everything globally available
@eddiemcconkie For the no-compiler-required functions I would imagine they would still just do the import for use behind the scenes. If this is the strategy I'm not sure there is much of a scope issue outside of having to document and stub out more "runes". As long as runes a prefixed with $
what naming conflicts are you envisioning?
I'm not too familiar with it but with tree-shaking isn't it also a non issue if they just put everything on a global $
?
You specifically called out transitions, isn't that already it's own module? https://svelte.dev/docs/svelte-transition
Not like untrack
which you import directly from svelte
. That could perhaps disqualify transitions from rune
status, and you would continue to recommend importing that when needed.
EDIT:
For reference, here is all the modules in svelte: https://github.com/sveltejs/svelte/tree/main/packages/svelte/src
I'm thinking it wouldn't be too crazy to have all these be rune
s... I'll have to look at it closer.
Tbf onMount, on destroy, before/after/onNavigate are all first-class framework features that have to be imported
I've only ever used SvelteKit, not svelte alone.
Didn't think about it when you mentioned onNavigate
... but that is definitely a part of SvelteKit and not plain svelte.
However if we ask "Is it function-like?" Answer: Yes. "Do you HAVE to import them from somewhere?" Answer: It's core functionality in Kit. "Is it part of the language?" Answer: It's part of the "language" of Kit. So, if we were to follow the modified definition of what we call a rune
, this means Kit could have $runes
like $navigate
, too.
You are right about onMount
, I misinterpreted those docs:
Additionally, you may prefer to use effects in some places where you previously used onMount and afterUpdate (the latter of which will be deprecated in Svelte 5).
Regarding my modified idea of what a rune
is, in regards to the issue you brought up of all the non-core modules that are still "part" of svelte, I went through them and have some thoughts:
<div use:foo={bar} />
import
needed.animate:fn
is an "Element directive" which is "part of the language" and requires no import.import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
<div animate:flip={{ delay: 250, duration: 250, easing: quintOut }}>
$rune
-like...
const allEasingFuncs = $easing.all()
<div animate:$animate.flip={{ delay: 250, duration: 250, easing: $easing.quintOut }}>
animate:fn
is already part of the language without being a $rune
.import { tweened } from 'svelte/motion';
const size = tweened(1, {})
$rune
-like...
const size = $motion.tweened(1, {})
import { writable } from 'svelte/store';
const count = writable(0);
$rune
-like:
const count = $store.writable(0);
import { onMount } from 'svelte';
onMount(() => {});
$rune
-like:
$onMount(() => {});
$effect
.
$tick
, $getContext
, etc...I understand that I am more or less hijacking the original intention of runes here to mean something that you would otherwise from svelte (or kit). You probably ascribe lower level meaning to rune
but I fear users will not.
I hope you can see that now because 5 is being a more expressive than the previously transparently overriding let
and what would otherwise be normal JS variables, etc, I am starting to see $
as a potential all-out prefix for what Svelte provides, and because of it being a compiler this should be possible to implement in the way I am describing, I think.
In fact, as a user, I want and expect this. But, as a framework author I understand you may value the original intention more than what users expect, and they will just have to learn that some very specific parts are compiled out (the current runes), but not all parts. The unfamiliar user will not know why this is the case, and may expect what I am saying here because they don't really care to know the reason why the first runes
were created (when previously they were essentially writing slightly enhanced JS), but they will make do either way I am sure. Personally, I still feel this is a viable idea, because you can have a technical document for those who want to learn more about how the internals work, explaining which "runes" are special.
Ultimately I will use Svelte either way, I just foresee a lot of users being confused by the difference in usage pattern between runes and other "parts of the language" provided by svelte/kit, and not really caring why it is different, and instead just wanting one consistent pattern if they could.
My thoughts are that only contributors and framework authors are going to want to understand the difference between $effect
and $mount
, although I could be coming too hard from my own userland perspective. Hope what I'm saying is coming across legibly, as I do believe what I'm saying is technically possible to implement, even if not desired or expected as someone who is thinking on a deeper level about the implementation of the primitives.
Essentially the whole import difference makes me want to have $rune
s in general, of which a subset are more "primitive" $rune
s that have no other way to be implemented.
A lot of words, I but I THINK I have a point. May be wrong though.
EIDT:
Just to get back to where this all started with untrack
as an example; I think boils down to usage versus implementation details...
This feels a lot more natural:
<script>
let size = $state(50);
let color = $state('#ff3e00');
let canvas;
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// this will re-run whenever `color` or `size` change
context.fillStyle = color;
$untrack(() => {
context.fillRect(0, 0, size, size);
})
});
</script>
Than this:
<script>
import { untrack } from 'svelte';
let size = $state(50); // Just doesn't feel right to have magic here...
let color = $state('#ff3e00');
let canvas;
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// this will re-run whenever `color` or `size` change
context.fillStyle = color;
untrack(() => { // But not any magic here...
context.fillRect(0, 0, size, size);
})
});
</script>
I feel it is not important for users to understand why untrack
is having to be imported for its functionality when state
and effect
are not, because svelte is compiler. As a user you just don't really care if it works and is easy. Now, if there is a very important reason why the user should understand this distinction, then maybe that trumps this idea.
Feel free to mark my comments as off-topic. Prolly should have started a different thread. Just thought it sounded like a plausible idea to put everything svelte-related behind $
and compile out the need for importing those things. Probably too many unknowns being different from standard JS and potential name collision between kit and underlying svelte, etc as @eddiemcconkie mentioned. Still an intriguing idea to me but probably impractical. Ppl will just have to wrap heads around the syntax that differs.
Untracking a value and untracking a block of code are two very different things. It feels quite weird that the current untrack
function do both. It untracks the callback content, then the returned value, if there is one.
IMO, the api should have:
$untrack(value: ReactiveValue)
to untrack a single value (the most common use case)untracked(callback: () => void)
to run many lines of code without triggering any reactivity@leoj3n
onMount
is deprecated in favor of$effect
in 5, but I'm not sure aboutonNavigate
.
I wonder if this still holds true? I know these docs are subject-to-change but. I don't see onMount
to be deprecated here:
https://svelte-5-preview.vercel.app/docs/deprecations
Also from this page: https://svelte-5-preview.vercel.app/docs/runes
I think afterUpdate
(the latter), will be deprecated. It's a little unclear though if the "latter" in this statement is
runes vs onMount & afterUpdate
; or onMount vs afterUpdate
Also for me, I wonder why onMount
should be deprecated? The $effect
rune will run every time a reactive $derived
and $state
inside it changes. Wouldn't that trigger unwanted executions?
If I wanted to run something strictly "on mount", (e.g. a useEffect without deps), then I would use onMount
, correct? If that were to be deprecated, what would I use in that scenario?
onMount won't be deprecated. It will just be shipped as const onMount = func => $effect(untrack(func()))
We decided against making untrack
a rune, given how rare its usage is.
Describe the problem
The
untrack
function feels a little clunky to use since you have to pass it a function instead of a value. I think it could be easier to use if you could just give it an expression.Describe the proposed solution
Would it make sense to swap the
untrack
function for an$untrack
rune? The preview docs for $derived says:and I think it makes sense to enforce the same behavior when untracking reactive variables to avoid side-effects. I feel like
$untrack(count)
is a bit easier to read thanuntrack(() => count)
. It would also be nice that you wouldn't have toimport { untrack } from 'svelte'
.Alternatives considered
N/A
Importance
nice to have