SC header logo

Svelte 5 - A Magical Revolution

Svelte 5 introduces game-changing features like runes and universal reactivity, transforming web development with faster, more efficient apps. Dive into how these innovations simplify coding, enhance performance, and prepare you for the future of JavaScript frameworks.

Author

Renato

CategoryDevelopment

If you're a web developer, you've probably heard of Svelte. You may think it's yet another JavaScript framework, however, according to StackOverflow's Developer Survey conducted in 2022, it was the most loved JavaScript framework at the time.

Svelte always prioritized developer experience, while producing light and fast results with minimal JavaScript. In fact, unlike React or Vue, Svelte doesn’t use virtual DOM. Instead, it uses a compiler to produce small, optimized, pure vanilla JS chunks that result in light and fast-executing code.

Challenges with Svelte 4

When Svelte came around, it utilized everything in JavaScript's toolbelt to improve developer experience. And they still do. It is as simple as you can get. For instance, within Svelte components, top-level variables are always reactive, e.g.

<script>
  let amount = 0;

  function add() {
    amount++;
  }

  $: double = amount * 2;
</script>

<button type="button" on:click={add}>Add</button>
<div>Current amount: {amount}</div>
<div>Double amount: {double}</div>

Whenever you would click the button, amount variable would update and template would follow. To have a derived value, i.e. to create a variable dependant on another variable, Svelte uses a special $: label that looks at the variables on the right side of the assignment and whenever one of them changes, it's going to update the variable on the left side of the assignment. It's perfect.

But, in reality, apps grow in size and complexity. Figuring out which variables are reactive and which aren't can get hard. Also, assignment reactivity works only at the top level of .svelte files, which can lead to unexpected bugs and problems when refactoring, splitting, or moving code around.

This is only the tip of the iceberg, but the topic of this article are not the problems of a potentially great technology, but the future solutions to the mentioned problems.

Runes - Where the Magic Happens

In September 2023, Svelte team introduced runes for Svelte 5. By definition, a rune is a letter or mark used as a mystical or magic symbol. In Svelte terms, runes are symbols that instruct Svelte compiler to do specific things. In other words, to achieve something "magical" in Svelte 5, you would use a rune. The magical symbol, that defines a Svelte rune, is the $ sign (jQuery fans will love this).

To understand runes better, let's rewrite the above code in Svelte 5 rune mode:

<script>
  let amount = $state(0);
  let double = $derived(amount * 2);

  function add() {
    amount++;
  }
</script>

<button type="button" onclick={add}>Add</button>
<div>Current amount: {amount}</div>
<div>Double amount: {double}</div>

Instead of every assignment being reactive, reactive variables are denoted by $state() rune. Also, there's no more special $: label - there's now a $derived() rune which denotes a variable that depends on another variable(s).

This might feel like a step back, as the previous approach was simple and intuitive, but runes make stuff much more transparent and easier to understand, as there is no magic happening in your code until you opt-in with a rune.

At the moment of writing this article, there are around 10+ runes in total. It doesn't mean there won't be more in the future. However, the most important ones you should understand are (but it doesn't mean you should neglect the others):

  • $state()

  • $derived()

  • $effect()

  • $props()

$state() rune

As the name suggests, $state() rune is used to define reactive state.

<script>
  let opened = $state(false);

  // plain objects and arrays are (deeply) reactive as well
  let selected = $state([1, 2, 3]);
</script>

$derived() rune

When a variable (trivially) depends on another variable, it can be wrapped with a $derived() rune to make it reactive.

<script>
  let price = $state(0);
  let priceInEur = $derived(`${price} EUR`);
</script>

$effect() rune

To run side-effects when something happens AND to replace lifecycle hooks, use $effect() rune.

<script>
  let opened = $state(false);

  $effect(() => {
    // This will run only once - when component mounts, because it
    // doesn't depend on any variables.
    console.log('Component mounted.');

    // This will run on mount AND whenever `opened` variable changes.
    console.log('State toggled:', opened);

    // This will run before the effect re-runs AND on component unmount.
    return () => {
      console.log('Re-running...');
    }
  });
</script>

$props() rune

To pass the props to a component, use $props() rune.

<!-- Cart.svelte -->

<script>
  let { amount, price = 0 } = $props();
</script>

<div>Price for {amount} items is {amount * price} EUR.</div>
<!-- Shop.svelte -->
 
<script>
  import Cart from 'Cart.svelte';
</script>

<Cart amount={5} price={12} />

But that's not all!

Runes are not the only new magical thing brought to Svelte. In fact, there’s a handful of new and improved features in Svelte 5.

Svelte 5 is a ground-up rewrite, meaning that its entire core was rewritten and improved, which means smaller, better and faster apps. Main optimizations include more efficient memory usage, faster compilation times, and reduced bundle sizes.

Also, it brings new and improved TypeScript support, which brings better type inference and more robust type checking.

Snippets and @render tags are another new thing. They are a way to create reusable chunks of markup inside your components to reduce duplications and possible bugs and errors.

{#snippet figure(image)}
  <figure>
    <img
      src={image.src}
      alt={image.caption}
      width={image.width}
      height={image.height}
    />
    <figcaption>{image.caption}</figcaption>
  </figure>
{/snippet}

{#each images as image}
  {#if image.href}
    <a href={image.href}>
      {@render figure(image)}
    </a>
  {:else}
    {@render figure(image)}
  {/if}
{/each}

But, one of the most important new aspects in Svelte 5 alongside runes is universal reactivity which means reactive statements/runes in files other than Svelte components. To enable reactivity in other files, they need to end in .svelte.js or .svelte.ts to tell Svelte compiler that runes are possibly present there.

// cart.svelte.js
export function createCart() {
  let amount = $state(0);

  return {
    get amount() { return amount },
    add: () => amount += 1
  };
}
<!-- Cart.svelte -->

<script>
  import { createCart } from './cart.svelte.js';

  const cart = createCart();
</script>

<button onclick={cart.add}>Add to Cart</button>
<div>Current items in cart: {cart.amount}</div>

Conclusion

At the moment of writing this article, Svelte 5 milestone is 97% done, which means there shouldn’t be that many breaking changes, and opting in runes mode should be fairly safe, even for smaller production code apps. Once it’s done, it could improve the entire Svelte ecosystem big time, and improve the JS developer experince once again. The time is now to get ahead and learn more about Svelte’s revolution. Who knows, maybe you’ll ditch some other frameworks in favor of Svelte.

You can check the resources below to find out more:

Further Reading...