Ha, did not expect this to be on the front page of HN today! Some quick context: in January I was in London for a few days, and while I was there we had a Svelte Society London event.
It's deliberately brief and vague in parts, because it's designed to spur conversation. It's not set in stone, nor is it an Official Statement on behalf of the Svelte team — it's an attempt to articulate the way that the maintainers tend to find ourselves thinking about some of these topics, as viewed through my personal lens. If it helps explain why you like Svelte, great! If it helps explain why you hate it, that's great too — that's the whole point. Have fun with it, and if there are parts you disagree with then your homework is to think through what kind of tenets would describe your ideal framework.
Thank you for creating Svelte. I have basic web dev experience using no-framework. Is there any high level overview article explaining how Svelte compiler transpiles Svelte file into a series of html,css and javascript and wires them up together?
Michel and I have been internet acquaintances for years, and we've even talked about this stuff IRL. MobX certainly isn't something we just somehow never learned about!
But anyway: it's absurd to compare this with React+MobX. MobX replaces useState, sure, but you're still re-rendering entire components on each change (which is why MobX explicitly recommends that you break apart your app into many small components, regardless of whether that's a boon to readability and maintainability.
By contrast, Svelte (and Solid, and other frameworks) understand signals on a much deeper and more optimal level. They're really not the same thing at all.
> MobX replaces useState, sure, but you're still re-rendering entire components on each change (which is why MobX explicitly recommends that you break apart your app into many small components, regardless of whether that's a boon to readability and maintainability.
This is not true. MobX has had an `<Observer>` component for years now. You can be as fine detailed as you wish with rerenders in a larger component.
Unlike Redux at that time (~2016), it was a first approach where minimum rendering was happening effortlessly.
You can have a list of components where each component references a bit of global state and rerenders only if that bit changes, even though the list might have enlarged or other elements changed.
Last time I used it (2016-2020), they used all of the tricks of the trade, get, set object attributes, dropped decorators and it still worked the same.
Notice that you just said "You can have a list of components _where each component_ references a bit of global state". In other words, in order to avoid re-rendering everything, you need to have a component for each item in the list.
In React, the component is the unit of re-rerendering. MobX can't change that fact. The only thing you can do is work around it with hacks that imperatively update the DOM.
Yes, this clarifies what you've meant and I can see the case that is not covered with MobX+React but I assume it is covered by Svelte's runes.
You're saying that I can have the whole app written in a single file without any separation and the updates will still happen only in the place that needs it.
That makes sense. With MobX, this could be done but not with React and not without a bunch of boilerplate that obtains html elements that are referencing the state.
>With MobX, this could be done but not with React and not without a bunch of boilerplate that obtains html elements that are referencing the state.
It's trivial with MobX. The Observer component essentially gives you an "inline" component wherever you need reactivity, without the need to actually componentize.
Cool, did not know that, last time I seriously used MobX was on 2016 primitives. But I see it works similarly to before. All of the accesses will be figured out during the first render.
Observer component is so simple. Just a deferred function invoke. Could have been done with 2016 primitives too.
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.
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).
In Svelte 4, the let counter = 0 syntax is already reactive by default, a feature enabled by the compiler. This has been the status quo for Svelte prior to the rune change. The introduction of the $state(0) rune actually provides more hints about its reactivity than before, and restore the original meaning of let counter = 0 (in rune mode). While it's true that the compiler's "invasion" into JS/TS syntax has been a point of discussion, this invasion has been happening for a while, and the initial shock wave has been well-absorbed by the community.
Interestingly, the new changes could be seen as a retreat from that initial invasion, likely triggering a different response from the community. In fact, the resistance I've seen (and my own as well) has been in the opposite direction—it's hating this retreat, complaining Svelte becoming less "magical." and more close to regular joe Javascript.
I’m specifically taking about non-component context, i.e. plain JS/TS files.
Previously Svelte was able to get a pass on this because magic only happens in svelte files - but in the future, any JS/TS files in a rune-enabled Svelte project will technically be SvelteScript, this never happened before and I doubt the community has already “absorbed” how significant this change is.
This is a really great point. I think something like a `.svelte.js` file extension is warranted here. This would key tooling to when it needs to interpret runes, and makes it clear which files in a codebase require the svelte compiler. These files clearly aren't just js/ts at this point, but I think its fine as long as they're marked as such. Custom react hooks, for instance, aren't usable outside of the runtime but can be transpiled without issues by esbuild/tsc and interpreted correctly by a js/ts language server.
As long as it's marked separately from js/ts I don't think its a huge issue though. Svelte files already have script tags that aren't completely vanilla js/ts.
FWIW you can of course implement createSignal in four lines of code, if you prefer the ergonomics of that:
function createSignal(initial) {
let value = $state(initial);
return [() => value, (v) => value = $state(v)];
}
Note that the Solid change is _not_ 'one step' — the `completed` property is being turned from a property to a function, which means you must update all the usage sites as well. Using getters and setters also allows you to use Svelte's convenient `bind:value` approach, which is much less verbose than using the equivalent event handler code. And don't get me started on the [type narrowing issues](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hzAgJwD...).
There's nothing _wrong_ with the Solid approach, but the ergonomics aren't to our liking. If we need to take a hit in terms of verbosity, we'd rather do it once at the declaration site than n times at every usage site.
These are great points! Thanks for the super thoughtful reply. I'm actually sold.
1. It's really nice that it's so easy to make a `createSignal`
2. I didn't realize that in Solid you had to update all the usage sites too, so now I'd rather stick to the Svelte usage. Nested reactivity is not as effortless as I thought in Solid.
get done() { return done },
set done(value) { done = value },
get text() { return text },
set text(value) { text = value }
Looks a bit boilerplate-y maybe, but keeping your preoptimized call sites is totally worth it.
This might be common enough that some syntax sugar might be worth it?
The short hand for:
todos = [...todos, {
get done() { return done },
set done(value) { done = value },
get text() { return text },
set text(value) { text = value }
}];
> Can you build a $derived from multiple other $derived?
Yes
> Will the end result see temporary, half-updated values?
No. It uses a push-pull mechanism — dependency changes don't result in a re-evaluation until something asks for the value, meaning derivations are 'glitch-free'
> the more Svelte you write, the more compiled code appears - generally, Svelte compiles to a size somewhat larger than the original source file.
This is one of those things that's more of a problem in theory than in practice, but nevertheless it's worth mentioning that Svelte 5 output is _much_ smaller than Svelte 4 output.
I assume that the Svelte 5 output is smaller because more stuff is being handled internally by signals, rather than the compiled output. Does that mean that the shared "runtime" code is larger, or have you just managed to shrink everything down here?
I'm very impressed by this stuff - I'm still not sold on the implicit reactivity, but I'm really excited about the pressure this is putting on the ecosystem as a whole and where that's going to go.
Yes. Signals are a wonderful mechanism, but they do come with headaches. Our goal was very much to adopt the elegant reactivity model without all the downsides, and we've approached this by making them an under-the-hood implementation detail that you don't interact with directly.
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.
> 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.
"...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
They mentioned that the team tried around 50 different variations. Meaning they spent a lot more time than a comment on this. I would trust they know what they are doing considering track record of Svelte team.
Tbh this seems kinda rude. Maybe check github i am sure the whole journey can be seen there.
I think the reason they didn't opt for this approach is that putting stuff in a {} block creates its own scope, so any variables declared within that block are inaccessible from outside said scope.
The $: label syntax is valid JavaScript, but allowing scoped variables to be accessible outside of scope is very invalid JavaScript.
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.