Thoughts on Svelte

Over the span of a month I built a personal RSS reader.

For the web client I used Svelte and SvelteKit, mostly as a way to evaluate whether I'd use the tools in larger projects.

Here are my thoughts on Svelte.

TL;DR

I enjoyed the Svelte experience on the whole. Highlights are the component format, built-in stores, and the event dispatcher API. Lowlights are reactive statements ($), await blocks, and built-in transition and animation APIs.

Component format

Svelte's component format is my favorite thing about it. When you write .svelte files, your default context is the same as the browser's: HTML.

This snippet from the Svelte docs (with some example markup, JS and CSS added) is illustrative:

<script>
  // logic goes here
  function add(a, b) {
    return a + b;
  }
</script>

<!-- markup (zero or more items) goes here -->
<p>1 + 2 is: {add(1, 2)}</p>

<style>
  /* styles go here */
  p {
    color: blue;
  }
</style>

Contrast this with React, where JavaScript is the default context and HTML is interleaved via JSX:

import '../some-styles.css'; // styles are imported into JS files

export function SomeComponent() {
  // logic can go anywhere
  function add(a, b) {
    return a + b;
  }

  // markup is returned from JS functions
  return <p className="some-class">{`1 + 2 is: ${add(1, 2)}`}</p>;
}

It's hard to pin down why this approach is great in real terms. You can't really say "Svelte's component format makes our team X times faster when building components than with Y framework" with any certainty.

I think that the component format is a big part of why people say they enjoy working with Svelte. There's a temptation to explain it as there's less context switching since the browser thinks about HTML first too, but I'm not sure that holds water. Either way, I like it.

Built-in stores

Svelte comes with built-in stores as an option for state management.

What should and should not be the concern of a user interface library/framework is a contested topic. I like that Svelte acknowledges that state management is a problem you will probably have and gives you a solution you can use or extend if you want to.

It's also nice that this solution isn't strongly linked to the component tree like React context is.

Event dispatcher API

Svelte has a built-in API that lets you create a CustomEvent, dispatch it, and listen for it on parent elements.

It's difficult to have an answer for the web's event model in systems that are built on the concept of a unidirectional data flow. By nature, the web's event model lets data flow upwards.

Svelte acknowledges that you might need to send data up the tree and gives you an API that uses web platform primitives. Thumbs up from me.

Reactive statements

I found Svelte's reactive statements confusing.

Svelte's basic reactivity is based on variable assignments. That I understand. The docs give this example:

<script>
  let count = 0;

  function handleClick() {
    // calling this function will trigger an
    // update if the markup references `count`
    count = count + 1;
  }
</script>

Makes sense to me.

Now, introducing the reactive $ label, the docs give this example:

<script>
  export let title;
  export let person;

  // this will update `document.title` whenever
  // the `title` prop changes
  $: document.title = title;

  $: {
    console.log(`multiple statements can be combined`);
    console.log(`the current title is ${title}`);
  }

  // this will update `name` when 'person' changes
  $: ({ name } = person);

  // don't do this. it will run before the previous line
  let name2 = name;
</script>

There is a lot going on here, and this is the doc's first example introducing the topic. Still, if you stare at it for a while it makes sense.

Add to that this gotcha from the Svelte docs:

It is important to note that the reactive blocks are ordered via simple static analysis at compile time, and all the compiler looks at are the variables that are assigned to and used within the block itself, not in any functions called by them. This means that yDependent will not be updated when x is updated in the following example:

<script>
  let x = 0;
  let y = 0;

  const setY = (value) => {
    y = value;
  };

  $: yDependent = y;
  $: setY(x);
</script>

This subtlety led to more than one case where I didn't understand why a component didn't update.

In the end I found it was difficult to determine reliably when to reach for the $ label. I'd use it in one scenario and it seemed to work like I expect, then throw it at another scenario and it didn't work like I expect.

So I avoided it altogether. One exception is I did use it to access store values. Unfortunately, it's also another use case that overloads $.

The $ label is one technical reason why I would be hesitant to adopt Svelte for larger projects. It's a core part of Svelte that you can't always avoid, and I think the potential for introducing bugs through it is high to start and very high at scale.

Await blocks

Svelte gives you {#if ...} and {#each ...} syntax as the primary control flow methods for markup rendering. It also has {#await ...} for deciding what to render based on the state of a Promise.

I like the idea, but in practice I always ended up refactoring it out. There was always something else I needed to do after the Promise resolved or rejected prior to rendering, and I didn't want to run that logic every time I used the service.

The logic also didn't belong inline in the rendering code, though. So where does it go?

Rip out {#await ...} and put it in the <script> logic, then use local variables when rendering.

I think it's not a bad thing that {#await ...} exists in Svelte core, I was just disappointed to find that the elegant use case in the docs is somewhat of a mirage.

Transition and animation APIs

My gripe with Svelte's transition and animation APIs is that I think they should be a concern of CSS and not Svelte.

Svelte gives you an elegant way to use CSS in your components with <style> tags, why not implement transitions and animations in CSS there?

I very well could have just not found a use case where I was glad for these APIs to exist, fair enough. Until I do, I will question why these not insignificantly complex APIs exist in Svelte's core.

Wrapping up

Well, these are my thoughts on my experience using Svelte!

I'd use it again for personal projects, maybe not for large company projects if I was the architect.

I also have thoughts on SvelteKit, but I don't have as much to say since I built an SPA, and so much of SvelteKit seems to be about server-side rendering.

Until next time!


Update (2023-03-28)

This note spent half a day on the Hacker News front page! There's a good deal of discussion worth reading.

In particular, comments from fenomas and illiarian shared insight on the transition and animation APIs. They are useful when you need to animate Svelte-managed elements as they enter and leave the DOM. Consider my gripe resolved.

Thank you all for your comments, they were fun to read!


Thanks for reading! Go home for more notes.