Readit News logoReadit News
EvanYou · 2 years ago
This is (surprisingly) almost identical to the Reactivity Transform explorations we did in Vue: https://vuejs.org/guide/extras/reactivity-transform.html

  let count = $ref(0)

  const double = $computed(() => count * 2)

  watchEffect(() => {
    console.log(double)
  })
We started experimenting with this ling of thought almost 3 years ago:

- First take (ref sugar), Nov 2020: https://github.com/vuejs/rfcs/pull/228

- Take 2, Aug 2021: https://github.com/vuejs/rfcs/pull/368

- Take 3, Nov 2021: https://github.com/vuejs/rfcs/discussions/369

We provided it as an experimental feature and had a decent number of users trying it out in production. The feedback wasn't great and eventually decided to drop it. https://github.com/vuejs/rfcs/discussions/369#discussioncomm...

rich_harris · 2 years ago
Hi! First up — not that it matters, but since people will wonder — our design wasn't informed by the Reactivity Transform. We evaluated something close to 50 designs, some of them extremely wacky, before settling on runes, and it wasn't until after that time that the Reactivity Transform was brought to our attention.

Nevertheless, it's interesting and validating that we landed on such similar approaches. While the Reactivity Transform failed, it did so for reasons that I don't think apply to us:

- $state and $ref are quite different. $state doesn't give you access to the underlying object, so there's no conversion necessary between reactive variables and ref objects (either in your head or in code).

- There's strict read/write separation. Anyone with access to a ref has the ability to change its value, which definitely causes problems at scale. It's something that React, Solid and Svelte 5 get right.

- Reactivity Transform introduces things like $() for magic destructuring and $$() for preserving reactivity across boundaries. We're instead encouraging people to use familiar JavaScript concepts like functions and accessors

- There are already a lot of different ways to work with Vue — SFCs vs non-SFCs, template syntax vs JSX, composition API vs options API, `<script>` vs `<script setup>`... on top of that, adding a new compiler mode that needs to interoperate with everything else is inevitably going to be a challenge. This isn't the case with Svelte. While both runes and non-runes mode will be supported for the next two major versions, meaning there will be some short term fragmentation, we've made it clear that runes are the future of Svelte.

EvanYou · 2 years ago
> $state and $ref are quite different.

I wouldn't say they are "different" - they are fundamentally the same thing: compiler-enabled reactive variables backed by runtime signals! But yes, Vue already exposes the underlying concept of refs, so for users it's two layers of abstractions. This is something that Svelte doesn't suffer from at this moment, but I suspect you will soon see users reinventing the same primitive in userland.

> There's strict read/write separation

I'd argue this is something made more important than it seems to be - we've hardly seen real issues caused by this in real world cases, and you can enforce separation if you want to.

> We're instead encouraging people to use familiar JavaScript concepts like functions and accessors

This is good (despite making exposing state much more verbose). In Vue, we had to introduce destructuring macros because we wanted the transform to be using the same return shape with all the existing composable functions like VueUse.

> There are already a lot of different ways to work with Vue

This is largely because Vue has a longer history, more legacy users that we have to cater to, so it's much harder to get rid of old cruft. We also support cases that Svelte doesn't account for, e.g. use without a compile step. That said, the default way with a compile step is now clearly Composition API + <script setup>. Reactivity Transform also only really applied in this case so the point you raised is kinda moot.

Separate from the above points, the main reason Reactivity Transform wasn't accepted remains in Runes: the fact that compiler-magic now invades normal JS/TS files and alters vanilla semantics. Variable assignments can now potentially be reactive - but there is no obvious indication other than the declaration site. We had users trying Reactivity Transform on large production codebases, and they ended up finding their large composition functions harder to grasp due to exactly this (and not any of the points raised above).

wentin · 2 years ago
Hi Evan You! (Hi from wentin) It's great to see you here in this thread. It's such a wonderful gesture for the open-source community to collaborate in this manner, by sharing valuable lessons learned the hard way.

It’s intriguing to see where the Svelte exploration will lead. Will it face disapproval or achieve success? While I agree that both implementations have arrived at the same place (almost serendipitously), their origins differ. For Vue, it's about adding the syntactic sugar by dropping .value, making it less explicit and more magical. In contrast, Svelte made the change to make things more explicit and reduce the “black magicness” from the svelte compiler, and bring it more similar to javascript. This difference might trigger totally different reaction, time will tell.

Looking forward to more insightful discussions!

vjerancrnjak · 2 years ago
All of this looks like MobX
ramesh31 · 2 years ago
>All of this looks like MobX

Shhhh. Don't tell the Vue/Svelte community that they're just reinventing the React ecosystem on a 5 year delay. It will spoil all of their fun.

tipiirai · 2 years ago
I guess we are all waiting for Rich Harris to step in and comment on this one. I'm sure he has followed this Vue experimentation and has a clear argument to make. At least I hope so.
Exuma · 2 years ago
Based Vue
frodowtf · 2 years ago
Funny, I also thought of Vue immediately.
satvikpendem · 2 years ago
This is basically what Vue and Solid do, no? Same sort of state and derive/computed variables, it seems like.

Also, I will never understand why people like reactive signals. The article even quotes "Knockout being right all along" which, no, reactivity and two way data binding creating a spaghetti mess of changes affecting other changes all over the place unless you're really careful is why React was made in the first place, to have only one way data binding and to re-render the entire page in a smart way. React will soon get its own compiler as well which will made regenerating the DOM even more efficient.

It's like every other framework is slowly rediscovering why React made the decisions it made. I am assuming it's because the users who are making these frameworks now have not used or do not remember the times where "signals" were called observables, or the usage of Rx libraries, and how having too many of these would cause you to lose your mind debugging the intricate webs you inadvertently spun.

rich_harris · 2 years ago
Yes, Vue and Solid — like Knockout — use a dependency tracking mechanism. Back in the day it was called `ko.observable`, nowadays we call them signals.

That's the part Knockout was right about. It's absolutely true that you can mishandle them and create a spaghetti mess, _if your design allows it_. Svelte 5 doesn't — it uses signals as an implementation detail, but in a way that prevents the sorts of headaches you're describing.

Signals and observables (e.g. RxJS) are related but separate concepts. The framework world is converging on signals as the appropriate mechanism for conveying reactivity — even Angular (which has historically been most closely tied to RxJS) is moving in this direction. I promise you it's not just a mass delusion.

hajile · 2 years ago
Not everyone has teams of perfect developers given all the time they want to make their perfect frontend app. Most of us deal with average teams where half the devs are at or below par while managing very tight deadlines. The spaghetti from everything in the whole system mutating always comes back to bite.

You call signals an "implementation detail", but if someone doesn't know that's what they are, then they'll wind up doing very bad things. This means they either aren't just an implementation detail or they are a hyper-leaky abstraction.

chrisco255 · 2 years ago
> create a spaghetti mess, _if your design allows it_. Svelte 5 doesn't — it uses signals as an implementation detail, but in a way that prevents the sorts of headaches you're describing.

Care to elaborate or do you have a link to a blog post explaining further?

satvikpendem · 2 years ago
Thanks for the reply, Rich. It seems that, given a large enough codebase, that sort of spaghetti mess will emerge on its own, as not everyone will be so thorough. Granted, I haven't used runes and based on your blog post here, I'm not reading where it would prevent such headaches as there's not much mention of that there, so I don't know exactly how it'd work, but just based on my experience using things like Vue and Knockout in the past, it wasn't too clean.

I was just mentioning in another comment how Rust has a similar problem where, as in C, you can mutate variables via pointers anywhere in the codebase, but Rust counters that by having a borrow checker that explicitly tracks the changes made and makes sure that only one user can modify a variable at any one time. An analogous concept might be quite interesting to implement in all these signal based frameworks as well.

no_wizard · 2 years ago
I always found observables need better nomenclature.

I know its not anyones fault (RxJS is amazing), but it does seem to invoke some gray area in the brain, thinking observables would do more heavy lifting around change tracking of inputs / sources, when its not that straightforward. They more rightly should be called SubscriptionStreams maybe

I think its always been somewhat poorly named, based on how often I've had to explain the concept to other developers.

klaussilveira · 2 years ago
Qt was always right.
lolinder · 2 years ago
Yes, the API is very similar to Vue's. $state is ref/reactive, $derived is computed, $effect is watch/watchEffect.

> why React was made in the first place, to have only one way data binding and to re-render the entire page in a smart way

This last part is why Vue and now Svelte didn't adopt React's model. Yes, reactivity and two-way data binding give you enough rope to thoroughly hang yourself, but it also gives you a very explicit picture of how and when components will update. React, on the other hand, expects you to trust its smart algorithms, which definitely removes a lot of the pain from day-to-day coding but also makes troubleshooting performance problems harder.

I see the distinction as similar to GC languages vs something like Rust: if all you need is to have an app that does a job and you don't care about how it does it, React is a good choice. If you need to have control over exactly what will update and when, Vue's model gives you fine-grained control over updates that's hard to achieve in React.

EDIT: And with that in mind, it's obvious why Svelte, which prides itself on being lean and fast, would avoid the React smart-but-complicated runtime model in favor of the Vue model.

hajile · 2 years ago
React is the pragmatic answer.

Most devs are of average talent and will hang themselves and everyone else with the stateful webs they weave. Even with careful PR reviews, these things have a way of sneaking in and becoming permanent hinderances.

Performance hasn't been a dealbreaker in most JS apps for years now. What HAS been a problem is getting good code out the door as fast as business needs. MOST React code is disposable based on the whims of the company. The bits that aren't disposable are used enough to justify having them designed and maintained by your best devs who should also have the skill to write performant code.

kabes · 2 years ago
You make it sound like react is a dark box with a lot of magic behind the curtains, but I always found the react model very easy to reason about. Modifying state is explicit and is the only thing that can trigger a rerender. These rerenders can only happen down from where in the tree the state change happens.
satvikpendem · 2 years ago
Now if only we had an analogous borrow checker in Vue and Svelte so that we don't hang ourselves with 2-way binding. That might a good research project, actually.
jbergens · 2 years ago
There was/is a state management lib called Mobx that was released years ago and was usually combined with React. It was kind of reactive, very easy to use and fast. It normally only updated the components that was effected by a state change.

It was a bit sad that it didn't get more popular and possibly improved. Some devs liked it but we were a minority compared to those who liked/used Redux.

One strange thing they did was to release a new "version" called Mobx State Tree which was very different. It had a great api but was different and slower.

jddj · 2 years ago
I'm replying to you, but I'm asking the room: what, if anything, stops vue3 from compiling down to pure JavaScript like svelte does and reaping the speed benefits?
berkle4455 · 2 years ago
What is the difference between $derived/computed and $effect/watch?
skrebbel · 2 years ago
I recently took Solid for a spin and I found none of that 2-way data binding mess you complain about. To be fair, I think you’re assuming is has behavior that it doesn’t.

Eg if a form input changes, that change triggers an onChange and in a handler function, you can let the change just flow through the data layer (ie through a signal or a store, which is just a hierarchy of signals). This then updates the input, but it already had that value so nothing happens. It’s pretty much the unidirectional loop that React people love to talk about.

satvikpendem · 2 years ago
Sorry, I should have been more clear, I was talking more about Vue and Svelte rather than Solid, which explicitly says they don't have 2-way binding due to learning about how dangerous it is and how React doesn't have it: https://www.solidjs.com/guides/faq#why-can-i-not-just-assign...
mmmeff · 2 years ago
What you’re talking about is unidirectional data binding, and what you’re replying to specifically mentions its failure to scale with complex applications.

It’s not the kind of problem you can appreciate when taking any framework for a spin with a form input.

lf-non · 2 years ago
> It's like every other framework is slowly rediscovering why React made the decisions it made

Nope. More like every other framework is slowly rediscovering that observables and dependency tracking offers the best DX and most of the innovations that React introduced have better alternatives.

diob · 2 years ago
Yeah, I have no desire to go back, regardless of "performance" gains. React keeps me sane and it's fairly simple to keep it performant.
willio58 · 2 years ago
I have a similar feeling. I keep up to date on changes to these other frameworks but in my work we've never run into React performance issues that couldn't be fixed by approaching things with good principals. Signals are brought up in convo every now and then as an approach to fix an issue, but stepping back we always seem to find a better solution but just re-writing bad React code. I'm sure if you're on a certain scale of users or extremely custom UI you could run into circumstances where signals would help you, but for our system that serves hundreds of thousands a day, we don't need them yet.
recursive · 2 years ago
I've worked on react codebases, and non-react codebases. Maybe it's a coincidence, but I find the react ones in general to be harder to debug. And not great for my sanity either.
synergy20 · 2 years ago
second this, plus its mature ecosystem, which is very valuable for the long term.

though, the SSR madness movement including React concerns me, everyone tries to mix CSR and SSR into one now, which makes things way more complex than it needs.

bhouston · 2 years ago
> It's like every other framework is slowly rediscovering why React made the decisions it made.

Definitely there is convergence in DX now. No need for everyone to admit one or another framework was right first or not - that is just creating antagonism or negating people lived experiences and innovation. But I do find it pretty weird that this convergence is happening. Initially I thought it was diverging with svelte but it seems to be reserving direction.

satvikpendem · 2 years ago
> * But I do find it pretty weird that this convergence is happening. Initially I thought it was diverging with svelte but it seems to be reserving direction.*

Convergent evolution [0]. Just as a shark, dolphin, and ichthyosaur all have the same body shape which is efficient for swimming and hunting in the water, so too do technologies converge when the problems are all the same and the solutions are understood, only as humans we can learn from each other rather than randomly mutating our codebases [1]. It's the same reason why we see a lot of languages starting to adopt more functional features such as not using `null` and having exhaustive pattern matching, such as Rust (from OCaml), Dart 3, recent versions of Java, etc.

[0] https://en.wikipedia.org/wiki/Convergent_evolution

[1] https://en.wikipedia.org/wiki/Genetic_programming

hajile · 2 years ago
It's not convergence, it's the cycle of development patterns. Patterns come and go leaving bad memories that become anecdotes. Anecdotes become "misunderstanding the paradigm" and even the misunderstandings are long forgotten when the pattern that made them comes into vogue again.
LapsangGuzzler · 2 years ago
I think this somewhat inevitable given the flexibility of javascript. The lack of convergence on one single paradigm as a long-lasting set of best practices speaks to all of things that a web framework needs to have in order to accommodate all developers.

In Svelte's case, it feels like they went as far as they could without directly addressing observables until they reached a point where it became one of the largest features that had yet to be addressed.

Open source frameworks, not unlike companies, need to continue attracting new users to grow the ecosystem, which means pleasing an ever-growing user base.

jjtheblunt · 2 years ago
> reserving direction

autocorrect style typo, i think

gedy · 2 years ago
> no, reactivity and two way data binding creating a spaghetti mess of changes affecting other changes all over the place

Honestly never understood this - 2 way is less code and way more understandable to me. Redux style codebases are very difficult for me to understand, and never saw benefits praised for. More frequently was "why isn't this updating?".

Different strokes I suppose.

unconed · 2 years ago
Indeed. You can get all the main benefits of two-way binding in a one-way dataflow system like React by using cursors... i.e. whenever you access a property `state.foo.bar`, also derive the setter/updater, `setBar(…)`.

With proper memoization, this is both fast and ergonomic, and it even allows you to turn mutations into data and pass them to a server. I've implemented this as a stand-alone state helper [1] based on JSON patch/diff, and it's magical. Undo/redo, multiplayer, it's all within reach.

[1] https://usegpu.live/docs/reference-live-@use-gpu-state

SarcevicAntonio · 2 years ago
> implying react codebases aren't spaghetti mess
jgalt212 · 2 years ago
> React will soon get its own compiler as well which will made regenerating the DOM even more efficient.

This will be amazing if works as people are hoping it will. I suspect it's an impossible task (in the general case). My hope is a certain coding style will be the path forward, and not a new version of putting shouldComponentUpdate (and others) all over the place.

WuxiFingerHold · 2 years ago
Architecture and the used reactivity mechanics are different things.

You need architecture to avoid a big ball of mud or spaghetti code. Organising your code in layers, modules, each with its own API. Rules which layer can access what other layer. Like backend does for ages. You can and should do this in large frontend SPAs as well. No matter if written Svelte, Angular or React.

cornfutes · 2 years ago
> I am assuming it's because the users who are making these frameworks now have not used or do not remember the times where "signals" were called observables

And also a tendency to not have experiencing working on large web apps (I’m talking 100k+ cloc) where issues arise, and why those React engineers made the decisions that they did

trueadm · 2 years ago
I work on Svelte 5 and I was also a React engineer on the core team previously FWIW.
Raed667 · 2 years ago
Two way data binding and signals are such bad ideas when dealing with large real-world apps. I feel they only make sense in small code snippets that are cool to tweet.

For me they're a dealbreaker when considering a new framework.

givemeethekeys · 2 years ago
Is it not possible to write Svelte code with one-way data-binding? Would that not give you the best of all worlds: compiled javascript, no virtual DOM, and easier to debug code?

Dead Comment

chrismorgan · 2 years ago
I’m not particularly comfortable with some of the nature of the change. Runes are magic compiler symbols, but they look even more like just normal code than was the case before. Previously, Svelte’s reactivity model was very easy to understand, including its limitations, because it was the result of very simple analysis—you can cover the rules in a few minutes without difficulty. It included some things that were obviously magic: $: reactive blocks and $ prefixes on stores.

When you had this:

  let count = 0;
  count += 1;
… it made reasonable sense, because that’s just normal JavaScript; the fact that `count` was made reactive was basically incidental.

But once it’s this:

  let count = $state(0);
  count += 1;
This looks like you’ve called a function named $state, and given that you’re talking about migrating from compile-time to runtime reactivity, you might (I think reasonably) expect `count` then to be some object, not just an integer, and so `+= 1` would be the wrong (since JavaScript doesn’t let you overload those operators). But no, it’s instead some kind of decorator/annotation.

Yes, stores are unwieldy as you scale them up, and definitely have some practical problems, but that createCounter stuff looks fragile. I’m curious how it all works, because it looks like it’d be needing to do quite a lot of control flow analysis, but not curious enough to investigate at this time. But my intuitions suggest it’s probably markedly more complex and difficult to explain, though perhaps and hopefully more consistent.

rich_harris · 2 years ago
> it looks like it’d be needing to do quite a lot of control flow analysis

Implementation-wise, this is vastly simpler than Svelte 4, because everything is explicit.

One thing we didn't really show today is how this works in your editor in practice — for example, TypeScript thinks `$state` and `$derived` are just the identity function. It makes sense when you use it, but I appreciate that I'm basically asking you to just trust me on that!

> Previously, Svelte’s reactivity model was very easy to understand, including its limitations, because it was the result of very simple analysis

I totally understand what you're saying. But in fact the analysis is _anything but_ simple — in places it's frighteningly complicated, to the point that even I sometimes don't understand what's going on! And that's part of the problem — because Svelte 4 is more implicit, you can't really know what's happening in a lot of cases, you just have to hope that the compiler knows what it's doing.

youssefabdelm · 2 years ago
I've seen the demo and you're right, in cases where the code gets more complex, it does look way cleaner. I also really appreciate that you guys made it so that you can still use Svelte in the same old way.

But at the same time I just wish there was some other solution to this while keeping it implicit somehow. I would do anything to keep things implicit while solving the backend complexity some other way. The thing I most love about Svelte is the principle / vector of "as little learning as possible", as well as in many cases transfer of learning, makes it much more user friendly, and I wish evolutions of Svelte continued to evolve along that direction, by reducing more and more.

But things like `$something` are a bit strange. Even `$:` is strange. It adds cognitive load. It's not very English-like and in turn not easily parseable. `on:click` is closer to the attractor of user friendliness. I think all programming languages and frameworks should try to approach English or Python, to allow for maximum "transfer of learning" so things just feel like they flow "without thought". Taking inspiration from UI/UX... things should be as close as possible to 'understanding at a glance'

The gold standard would be to approach something like this level of intuitiveness in the future as crazy as it may seem: https://twitter.com/brianjoseff/status/1617556877218570241https://twitter.com/mathemagic1an/status/1700232760756207956...

I have many ideas on how, but it would no longer be a programming language.

eyelidlessness · 2 years ago
> for example, TypeScript thinks `$state` and `$derived` are just the identity function

That seems like a missed opportunity on Svelte’s part… but hard to fault, because TypeScript doesn’t support nominal primitive types very well. Ideally it would be something like Reactive<T> to signal (ha) that it’s not just a plain value.

chrismorgan · 2 years ago
Fair enough. I’ll see how things go and wish you well. For simple cases, the new will be syntactically-inferior, but it makes sense that the explicitness allows it to be more consistent. Ah, modelling mutable trees as stores… not a great deal of fun.
jameshart · 2 years ago
Functions can return primitives, so there’s nothing wrong with $state() returning a number that you can +=1.

What is weird is that it’s actually typed as a svelte-compiler-only ‘extended int’ that can have side effects when you increment it, and weird knock on results from reading it.

ranting-moth · 2 years ago
Perhaps I'm just the grumpy old guy that's afraid of change.

But I fell in love with Svelte because it was dead simple (according to me). It was a breeze of fresh air and I felt I just program again without wiring enchantments together.

I do agree that its simplicity has downsides too, so perhaps it's just nothing to be afraid of?

But I can't help seeing this as ominous:

> This is just the beginning though. We have a long list of ideas for subsequent releases that will make Svelte simpler and more capable.

Please don't turn Svelte into a black magic box.

sanitycheck · 2 years ago
Largely agree.

I've got a couple of thousand hours of Svelte dev experience, and these changes offer me nothing I really want.

Svelte dependencies outside my .svelte files? No thanks, I'll keep them usable in non-Svelte projects.

A new way to do reactivity to try to appeal to React users? ("When people come to our community in future..." in the vid.) No, thanks! Svelte 3/4 reactivity was very straightforward, one could learn the whole thing in a day from the excellent tutorials. It was better than React.

There was definitely a bit of weirdness with overuse of reactive vars but that's been incentive to keep components small and simple. A good thing!

Personally I'm still stuck on v3 because 4 introduced breaking changes I haven't had a chance to debug, so it'll be a while until any of this impacts me anyway.

phero_cnstrcts · 2 years ago
I’m definitely getting some react vibes from this. And I hate react.

Maybe I just need to try it out.

ansc · 2 years ago
Agree. Svelte is a blast to write as an old grumpy backend engineer. But yes, probably just scared for nothing. Turning onMount() into $effect() is a tiny change, but this self-aware smol brained grug likes onMount() as it means something! To enable logical grouping, Svelte 5 allows us to nest the $effect()s? What does that even mean? Where am I? Nurse!
fyzix · 2 years ago
nesting means that a variable being watched in the parent scope triggers both the parent and child functions, but a change to a variable in the child's scope only triggers the child function.
698969 · 2 years ago
maybe super specific to this comment but onMount isn't going away, it could be replaced with $effect from an implementation standpoint, but it would be wildly confusing if used for that purpose without the clearer name for the wrapper
benmccann · 2 years ago
This set of changes makes things more explicit. That's the opposite of a black magic box as far as I can see
pier25 · 2 years ago
Definitely. It's surprising to see all these magic comments.

Maybe it's because of using the "runes" word :)

sod · 2 years ago
Was my first gut reaction as well. Especially when i saw `$state(0)`.

But watch the introduction video. It isn't at all like the functional reactivity that entered other frameworks. It's still the svelte way. Read and assign is plain javascript. Only the initialization syntax changed.

They even promise backwards compatibility, so you don't have to migrate or migrate file by file if you want to. Which is also not how other frameworks handled this in the past.

wentin · 2 years ago
One of Svelte's biggest advantages is its compiler, positioning it more as a language than just another JS framework. If I'm not mistaken, the compiler allows Svelte to define its syntax to anything they want.

Given this, I'm curious: couldn't the traditional syntax of `let counter = 0` be made to function similarly to the new let count = $state(0);? Transpile it to let count = $state(0) under the hood? If that can work technically, instead of introducing a new rune for reactivity, why not introduce a "negative" rune to denote non-reactive statements? This way, the change wouldn't break existing code; it would be more of a progressive enhancement.

I agree the move to unify the Svelte <script> tag with regular js/ts files is an improvement. It was indeed a little odd that certain syntactic sugar, like the $, would work exclusively within the Svelte <script> and not in a js/ts file that's right next to it. However, from what I gather, it seems the Svelte team is aligning the Svelte script more with js/ts, rather than bringing the js/ts closer to Svelte's unique syntax. This trajectory seems to be pushing Svelte towards resembling traditional JavaScript frameworks, like React. It's a departure from Svelte's unique strength of having a custom compiler and behaving more like a language. If every syntax in Svelte is expected to mirror its behavior in js/ts, eventually svelte will lose all it secret sauce that made it so unique. Why can't we add a rune into js/ts file, a note in the beginning to tell svelte compiler that this is svelte enhanced js/ts file, compile it like svelte script tag code? Bring js/ts more alike to svelte?

rich_harris · 2 years ago
We evaluated somewhere close to 50 different design ideas (seriously) before settling on this one, and what you describe was one of those ideas.

But one of our goals was for you to be able to use Svelte's reactivity inside .js/.ts files, since that's one of the things people have been crying out for since we released Svelte 3. For that to work, reactivity has to be opt-in, not opt-out.

And that's how it _should_ be — someone reading the code should be clued into the fact that this `let` won't behave like a normal `let` in JavaScript, and it should be possible to move code between modules (and between modules and components) without worrying about whether a specific file was opted in to certain behaviour.

In other words this...

> This way, the change wouldn't break existing code

...isn't quite right — it would break _all_ your existing code that wasn't in .svelte files.

> If I'm not mistaken, the compiler allows Svelte to define its syntax to anything they want.

On this point specifically: unfortunately not. The code in a .ts file, for example, has to be valid and typecheckable. You can't insert a preprocessing step ahead of the TypeScript compiler. Again though we concluded that this is a good thing, since it means this all works with all existing ecosystem tooling.

aradalvand · 2 years ago
> for you to be able to use Svelte's reactivity inside .js/.ts files, since that's one of the things people have been crying out for since we released Svelte 3

I can't find an issue for that among top 50 open issues on Svelte's GitHub repo. I've also been a member of Svelte's Discord server for years and do occasionally hang out there, I haven't seen people "crying out" for this at all.

On the other hand, there are things like `<style module>` or `forward:class`, etc. that people have ACTUALLY been crying out for (literally one of the most upvoted issues in the repo) which haven't been addressed at all.

Phillippe · 2 years ago
"...isn't quite right — it would break _all_ your existing code that wasn't in .svelte files."

What if it is opt-out reactivity in .svelte files and opt-in reactivity in .ts/.js files? Yeah I know it would be a bit more combersome to copy code from .svelte to .js/.ts files but I think it would be worth it

wentin · 2 years ago
First off, thanks for chiming in with that detailed explanation. It's always a learning curve when you dive into the technical side of things, and I genuinely appreciate it.

Given the constraints with TypeScript not being further compilable, I've been pondering on Svelte's direction. Personally, I'd lean towards letting go of some of the special touches in js/ts file if it means keeping more magic in Svelte. If we're heading towards making Svelte syntax work exactly the same in js/ts entirely, it feels like we might risk turning Svelte from its unique language-like identity into just another framework in the JS world.

Thanks again for the insights! I have already felt a little better about my current work in migrating an app from another framework to svelte 4. I was worried that I have made a very wasteful decision for my own company.

spacebuffer · 2 years ago
> Given this, I'm curious: couldn't the traditional syntax of `let counter = 0` be made to function similarly to the new let count = $state(0);? Just transpile it to let count = $state(0) under the hood? If that can work technically, instead of introducing a new rune for reactivity, why not introduce a "negative" rune to denote non-reactive statements? This way, the change wouldn't break existing code; it would be more of a progressive enhancement.

exactly my thoughts, feels like they could've kept it much simpler

Deleted Comment

pimterry · 2 years ago
It's great to see people moving in this direction, but I'm disappointed that everybody has decided to reimplement basically the same thing independently.

Mobx was ahead of the game here (though, granted, it too draws on Knockout.js). You can use Mobx to declaratively describe reactive state, much like this. But it isn't integrated into any framework - you can use it in vanilla JavaScript with no other runtime or compilation required. You define models with Mobx, then on top of that there's mobx-react, which ties your model into React's update APIs (essentially making render() automatically observe values, and making relevant changes trigger a re-render), or there's mobx-vue, or mobx-svelte, or mobx-preact, etc etc. Decoupling this is super helpful - for example I'm using mobx to model raw state, computed state & reactions to changes in server side Node.js, no problem.

Meanwhile, recreating the same reactive concepts independently in each library instead makes all the resulting code incompatible, so even your pure JS state models are coupled to your UI framework - e.g. createCounter in this post has to import & use $state from Svelte. That makes it far harder to change frameworks in future, hard to share code between apps using different frameworks (plausibly even different versions of the same framework), etc etc.

I'd love to see a native common standard for this, similar to how promises were eventually standarized and suddenly everything had a compatible model for handling async future results (I could swear I've seen initial discussion on exactly that already, but I can't find it anywhere now sadly).

gabeidx · 2 years ago
You may be thinking of the ECMAScript Observable proposal: https://github.com/tc39/proposal-observable
pier25 · 2 years ago
I loved MobX. Used it for years with React and Inferno.

But not only it's an added layer of abstraction, it's also a complex piece of machinery which is not needed when the framework solves reactivity.

When I started using Svelte back in 2019 I was very happy to let go of MobX.

Rapzid · 2 years ago
This is the way.

Mobx tc39 decorator support is pretty much done too. I check in on the PR weekly haha.

jauntywundrkind · 2 years ago
It kind of had to get sent back to the drawing board to work well (avoid all the absurd one-at-a-time limitations of generators), but async-iteration-helpers I think will be an incidental huge help in normalizing/nudging the consumer api into "just an async iterable". Updating/writing state might be more complex, speak to the implementation, but seeing things change should have a semi-standard api. https://github.com/tc39/proposal-async-iterator-helpers

One huge sadness I have is that when you have a callback in .then or .next, your handler doesn't get a gratis reference to what you were listening to. I'd love to have this be a better record. DOM is so good about having thick events that say what happened, and it's so helpful, sets such a rich base, but js snubbed that path & left passing data in 100% to each event producer to decide as it will. Consumers can craft closures to seal some data into their handlers but it sucks compared to having an extensible data flow system. We're kind of sort of finally fixing this crime the hard way by standardizing async context/async_hooks, which is a whole new ambient way to create context that we can enrich our async events with, since there was no first class object to extend for having omitted passing in the promise to the promise handler. https://github.com/tc39/proposal-async-context

Also worth pointing out, maybe arguable as a hazard tale, observables was proposed a long long time ago. There's still a ton of adoption. But it also feels extremely similar yet notably apart & worse ergonomics than async iteration. It's imo a good thing it didn't get formalized. https://github.com/tc39/proposal-observable

Alas one reactivity I really wish we had had is object.observe (note: different API than observables), or something like it. Undoing good specification work & saying, 'yeah, if users want it, they can implement it themselves with proxies' has lead to no one doing it. And you can't just observe anything; whomever is the producer has to up front make the decision ahead of time for all consumers, which turned a great capability into something first boutique then quickly forgotten. Alas! https://esdiscuss.org/topic/an-update-on-object-observe

I ack your ask, and there's no shortage of options that are pretty damned good. But I feel like we are still in a discovery not deployment phase, still delay competing because we have a lot of terrain and idea space to consider. Before we pick, standardize & ship one.

mbStavola · 2 years ago
As a recent adopter of Svelte, these changes are intriguing but I don't really have a grasp of how I feel about it quite yet.

One thing that I am definitely happy to see is the removal of $: as it should help Typescript users. Personally, I was quite sick of writing:

  let input = 'Hello';
  // ...
  let loudInput: string;
  $: loudInput = `${input)!`;
Instead of:

  let input = 'Hello';
  // ...
  $: loudInput: string = `${input)!`;
It's an incredibly minor thing, but when you do it 1000 times it becomes very frustrating. Having reactivity be rune-based should help TS avoid the syntactic confusion, bringing us to:

  let input = $state('hello');
  // ...
  let loudInput: string = `${input)!`;

rich_harris · 2 years ago
Having a great experience with TypeScript was very much one of our goals. Many design ideas failed to clear this hurdle
aradalvand · 2 years ago
That specific TS issue has literally been fixed for ages. What version of Svelte were you on?!
jasim · 2 years ago
What about long arrays? Is there a mechanism where Svelte knows which element is mutated, and do fine-grained recomputation/update only the corresponding view elements?

This is the primary place where we have to go outside the framework in React. Only a large linear list has this problem -- if it was a decently balanced component tree, then we could skip updating huge swathes of it by skipping at a top level node. But it is not possible in an array. You have it iterate through each element and do a shallow comparison to see if the references have changed.

That said, it is fairly easy to escape to DOM from inside a React component with useRef and the portal pattern. Then we can write vanilla JS which makes updates to only the values it changes.

If Svelte solves this in an elegant manner, then it would be a very compelling feature over React. I'm asking here because last I checked, most of the examples in Svelte's documentation pointed to simple counters and regular objects, and I couldn't find an example of a large linear list.

nicoburns · 2 years ago
Did you try setting the `key` property in React?
jasim · 2 years ago
`key` informs React of the identity of an element. That helps it during the reconciliation phase -- if it knows only one `key` in a list of DOM elements has changed, then it will run the DOM updates only on that one. Similarly if the order has changed, it only needs to move its index in the parent DOM element.

But it doesn't help in the rendering phase - aka when the virtual DOM is constructed when `render` is called on the root component, and all our JSX code is executed across the component hierarchy. Virtual DOM reconciliation cost is only a part of the performance penalty, re-running the "view as a function of state" computation is another major chunk.