Impressions of svelte

The sidebar component is my first project using svelte. This post contains a collection of thoughts on svelte. The focus is not on describing svelte’s concepts or syntax. Svelte.dev does an exemplary job at that. My opinions are mostly based on the comparison with react. I’ve dabbled around with the original angularjs, and played for fun with vue two years ago, but my habits built up in four years of using react professionally.

Setup experience & 1st code

It’s easy enough to bootstrap a svelte project using degit. Where create-react-app is a bloated mess full of bandwagons of hidden configuration, the svelte starter is a slim, no-frills project. How is it that react still doesn’t have a good onboarding experience? I’ve never used angular’s CLI, but I can sing the praises of vue CLI. Compared to that, having exactly one preset for svelte projects is a bit skimpy.

That’s more than made up for by svelte’s tutorial, which moves in small, easily understandable, interactive steps: You can go there right now and skim over it in an hour. Small code pieces go hand-in-hand with short explanations of the concepts. The approachability hides the surprising amount of depth covered. No question: Readers of the tutorial with previous knowledge of a modern javascript framework and web development will be able to build applications right after. Good luck researching the best solution to manage data in your react project in that time. Svelte’s onboarding curve is even less steep than I rember vue’s, whose docs I adored. For those who want to play around to get an understanding, the REPL is a good start. It showcases a preview next to the surprisingly readable results of the svelte compiler – which is far more decipherable than the output of a typical babel plus webpack setup.

Back to the bootstrapped degit project: it works, it’s small, and therefore straight forward to start with. The fact that the starter project uses rollup instead of webpack is a source of joy. During the first few hours I found myself copy-pasting from the different cases covered in the tutorial a lot. As the tutorial’s examples are well-chosen, they apply to a wide range of situations new starters will likely find themselves in.

Quickly, a warm feeling settles in: svelte feels complete and polished.

Developing a javascript web app with svelte gets close to the effortlessness of developing a static website. That’s for one because components are look-alikes to an HTML file with inline style and script. Secondly, svelte can be used without third party libraries. It covers basics like working with (scoped) styles, provides effective tools to deal with async requests, application-wide data, and even luxury features like transitions and animations.

Syntax oddities

Template syntax

Svelte comes with its own template syntax, which is closer to angular’s and vue’s than jsx. It makes for a HTML-alike feel when authoring component’s markup. Designing a svelte specific template syntax has enough trade-offs that I’m wondering about the motivation not to re-use battle-proven syntax already familiar to javascript developers.

The same arguments could be made about vue’s or angular’s template syntax – but they were first. It seems wasteful to reinvent the wheel, given that developers have pre-existing knowledge .

To be clear: None of the above is hard. But the svelte authors made some strange choices when it comes to syntax: Why is an if condition beginning with a different control character than the condition’s end?

{#if condition === true}
<p>it's true!</p>
{/if}

The first few days, I typed {#endif} more times than I care to count. Not a major turnoff, but most people onboarding will experience unnecessary friction when starting due to small choices like this.

Props

Svelte’s syntax can be eccentric not only in templates. See how a component Button defines that it expects the property size:

<!-- Button.svelte -->
<script>
	export let size
</script>

It feels strange that the component receiving a property exports it as a mutable let. Using let let’s me shudder a little bit every time. For me, using let is like consciously adding a code smell, it’s dirty. Feelings are no strong argument against the syntax. But I’m probably not the only one who finds this counterintuitive – after all we javascript developers spend the last years embracing typing (flow, typescript), or at least the concepts of immutability (e.g. with the flux pattern, immutable.js). Whenever I stumble across a let during code review I will triple-read the function. Usually there’s a more readable and preferable versions without let. Re-assignments can trigger all sorts of subtle bugs!

Now let’s say I want to add the following variables as props to Button:

const size = 'large'
const primary = true
const onClick = () => alert('you clicked me!')

In react individual props can passed inline without destructing.

<Button size primary onClick>
	My button
</Button>

In svelte, passing props is more repetitive and therefore, once a certain amount of props is being used, hard to read:

<Button size={size} primary={primary} onClick={onClick}>
	My button
</Button>

This is not entirely fair - it’s possible to spread an object of props. It just requires the component to assemble and destruct an object of props in curly braces – not quite as simple, as the react example:

<button {... size, primary, onClick}>My button</button>

Yes, I’m nit-picking here - it’s just that in other areas svelte minimizes the syntax overhead better. Like for example when attaching a one-time event listener. Similarly to vue it’s boiled to the minimal possible syntax:

<button on:click|once="{handleClick}">
	<slot></slot>
</button>

Let’s move on to another one of svelte’s strong cases.

Styling

It’s comfortable to write automatically scoped styling next to its markup. Vue paved the way here with scoped CSS.

On top of taking care of scoping, svelte comes with elegant syntax to bind classes conditionally.

Class binding

Here’s how to bind classes from props with svelte sans third party libraries:

<script>
	export let size // one of: 'large', 'small'
	export let primary // one of: true, false
</script>

<style>
	.size {
		...;
	}

	.primary {
		...;
	}
</style>

<button class:size class:primary="{primary}">
	<slot></slot>
	<button></button>
</button>

The pure react equivalent would be much harder to read:

export const Button = (size, primary) => {
	return (
		<button className={`${size ? size : ''} ${primary ? 'primary' : ''}`}>
			{children}
		<button>
	)
}

This syntax pain enabled the rise of small helpers like classnames:

import classnames from 'classnames'

export const Button = (size, primary) => {
	return (
		<button className={classnames(size, { primary })}>
			{children}
		<button>
	)
}

It’s not a coincidence that CSS-in-javascript gained massive traction.

The argumentation from the react crowd is that react gives freedom to the developer instead of enforcing specific solutions. Every single developer using react has to decide on a way to handle styling and overcome the friction of setting it up. I suspect that facebook’s upcoming CSS-in-javascript solution will soon be seen as standard way to style react applications, proving the point that developers expect their main frontend framework – react is a library, I omit some precision for readability – to provide a sensible default.

Unused style detection

But svelte’s most unique (compiler enabled) feature is its automatic detection of unused styles. Having experienced this comfort, my eyes glaze over with irritation whenever I during PR review once again find myself comparing styles and a component’s jsx in a GitHub div because an existing component was refactored and I need to ensure that all remaining styles are being used.

How to compose pages from components?

What I’m missing, is a pattern to compose pages with reusable components. When using a component, I often need to define small style variations that are unique to the specific usage. This might be setting a margin or display property, a unique hover color, or a special width.

<!-- LoginPage.svelte -->

<script>
	import Button from './Button.svelte'
	...
</script>

<main>
	<h1>Login</h1>

	...

	<!-- button is supposed to be full width -->
	<button {...props}>Login</button>
</main>

vs.

<!-- SearchBar.svelte -->

<script>
	import Button from './Button.svelte'
	...
</script>

<style>
	/* darn, no way to target <Button/> */
</style>

<input bind:searchTerm></input>
<!-- button is supposed to be in line with search input -->
<Button {...props}>
	Search
</Button>

How would I do that?

Foresee every different possible variation of Button, and extend Button with a bunch of optional props to allow to customize it to all of the possible versions? I’d never stop having to rewrite Button, and end up with an unmaintainable component signature.

Or add a style property to every reusable component and pass a value in every usage that requires customization? Probably.

It would be nice, could I attach a class to Button and target it in the component that uses it. Though that could only work for components returning a single DOM node, which is not required by svelte.

Common use case examples

Reactivity & local state

Let’s get back to the criticism that svelte uses let. It makes defining and updating state as breve as possible:

<script>
	let clicks = 0

	function onClick() {
		clicks++
	}
</script>

<button on:click="{onClick}">
	<slot></slot>
</button>

No need to remember arbitrary setter syntax. I’d say: “beautiful!” – if there weren’t gotchas. To update e.g. arrays or objects, assignments can be necessary. The following won’t do the trick:

<script>
	let clickTimes = []

	function onClick() {
		clickTimes.push(new Date())
		// use assigment to make it work: clickTimes = clickTimes.push(new Date())
	}
</script>

<button on:click="{onClick}">
	<slot></slot>
</button>

Now, push is a mutating function, and therefore a little bit conspicuous anyway. Its usage is easy to replace. But wouldn’t any javascript developer expect a push on a let to update the value? Definitely! You see: me and let, we’re not becoming friends!

So there is some magic going on when updating state. It’s not the only sprinkles of it. Svelte rightfully markets its easy to use reactive declarations / statements, where a component update causes a re-execution of a computation. To define reactive code, prepend it with $:. That stuff is powered by labeled statements. Never heard of it? Me neither! But… it works.

Store & state management

Where component state in svelte have the simple syntax of variables in react’s render functions, svelte’s stores are as simple as react’s useState hook. Without further introduction a copy-paste of svelte’s tutorial:

import { writable, derived } from 'svelte/store'

export const name = writable('world')

// `greeting` re-computes whenever `name` is updated
export const greeting = derived(name, ($name) => `Hello ${$name}!`)

The usage:

<script>
	import { name, greeting } from './stores.js';
</script>

<!-- read the computed greeting from the store -->
<h1>{$greeting}</h1>

<!-- 😍read from and write to the store by binding `name` to the input -->
<input bind:value={$name}>

<!-- update `name` from anywhere with re-assignments like local state -->
<button on:click={() => $name += '!'}>
	Add exclamation mark!
</button>

Couldn’t be simpler, right? Well, the devil is in the detail: $, which with colon would signal a reactive declaration / statement, stands for “This is a store value. When you unmount the component svelte, please remove the subscription to the store to prevent memory leaks”. It’s easy to imagine confusing components. I guess using stores and reactive declarations in the same component is an obvious anti-pattern. No big loss, as the reactive computation can be moved to the store.

Async data fetching

No web app without network calls. Network calls mean hassle: showing a UI state during the loading, providing a fallback UI in error cases, and most importantly, the desired view state when the data are present. In react, handling network calls and connecting them to the UI states is often done with libraries. Suspense will ease the …suspense. Svelte again keeps it simple:

<script>
	let promise = getDataFromAPI()

	async function getDataFromApi() {...}
</script>

{#await promise}
<p>loading</p>
{:then userName}
<p>Your username: {userName}</p>
{:catch error}
<p>{error.message}</p>
{/await}

Transitions & Animations

How awesome it is, to drop in a one-liner and have working transitions or animations!? With svelte, it’s that simple:

<script>
	import { fade, fly } from 'svelte/transition'
</script>

<button in:fade out:fly>
	<slot><slot>
</button>

Let me admit, that the above is oversimplified. Not because it doesn’t work perfectly well, but because I’m leaving out that much more complex transitions and animations are in reach. Extension with self-written CSS or javascript transition, transition events, orchestrated animations with an animation directive - easy peasy!

Other features worth mentioning in passing

There’s many more features that can be compared to react and vue in some way or another. For example:

Performance

Bundle size

Let’s move to the topic of the impact of the framework on the amount of code shipped to users. There’s two reasons to care about that:

It’s not possible to know how relevant bundle size is and what the best bundling tradeoff for a project is without having context on the application’s use-cases and users.

After this detour, let’s get back to bundle size: It’s entirely possible to use svelte, a full-featured framework capable of delivering complex SPA applications with all of its developer comfort just to sprinkle some small interactivity into a regular homepage, and come out with a fraction of the amount of javascript one would ship, if one were to implement the dynamic behavior by writing a couple of lines of jQuery.

Traditionally using any library / framework like react or vue requires to ship the framework as a whole to the user, at considerable base-line fixed costs. At the same time, their code provides abstractions that allow to add more application code without linearly increasing the bundle size. Svelte behaves the opposite: The hello world example is compiled to 3.029 bytes - it’s hard to get the code much leaner with a framework (preact and hyperapp allow for similar sizes). As components are compiled to pure javascript without a lot of shared helpers, there’s repetition though (see svelte discussions like 1, 2).

For supremely large applications, using react may result in a smaller bundle then when using svelte 1. My sidebar project was too contrived to get close to that point. When the bundle size of svelte applications realistically surpasses the size of other popular javascript libraries / frameworks, and whether that results in unfavorable performance, is an ongoing discussion in the svelte community. So far no one has raised actual problems - which either means that no one is yet using svelte for big applications, or that svelte shows good enough performance characteristics in those cases.

Rendering Performance

Other’s have benchmarked svelte in comparison to pure javascript and other javascript frameworks, with good enough results. This is insofar noteworthy, as in that svelte does not rely on a virtual DOM. The linked benchmarks mainly stress test rendering related activities which occur in every typical web application, and boot-up time. The abstraction of comparing the smallest common determinator naturally limits the meaningfulness of (the) benchmarks - it’s hard to know how a frameworks code would perform for your application without porting it.

Even if one were able to pick the fastest javascript framework when it comes to rendering, the UX would still depend more on whether one would use the framework correctly. At least with react – but probably all frameworks – it’s easy to accidentally cause countless expensive re-rendering cycles in actual usage by composing components badly. How data loading is handled has drastic impact as well given that network roundtrips are often more time intensive than the execution of some badly written javascript.

Most javascript frameworks are good enough when it comes to raw rendering performance, and svelte is no exception. Raw rendering performance is a weak signal in 2020 when arguing for or against a framework.

Ecosystem

Let’s not kid ourselves: There’s no ecosystem to speak of around svelte besides sapper, a next clone. Even sapper seems half-abandoned the past few months. That’s natural given that svelte is small, and there’s enough competition of other amazing javascript frameworks out there. As svelte comes complete out of the box, there’s rarely a need for a lot of svelte specific utilities though. Getting help can be a bit trickier, as the discord is mostly filled with developers starting with svelte. Not the best place for detailed conversation given its ephemeral nature.

Conclusion

Coming from react, svelte’s syntax is at times unnecessarily eccentric. But, once used to it, the hurdles of entry are uncharacteristically low for a modern javascript framework. Svelte is coming with single file components, and the co-location of javascript, CSS, and HTML template feels a lot like developing a static website with inlined javascript and CSS.

In addition to that, svelte comes with built-in solutions for use-cases developers will run into when working on a biggish SPA. Unlike react, there’s zero project setup choices to make when getting started on a web app. Svelte can afford to provide niceties and niche features. Most might not be used by everyone – and therefore they are rightfully not part of react, which is already too heavy as is – but thanks to the compiler, svelte’s functionality will only be part of the shipped code if used. It’s the opposite approach to concatenating a framework’s code with dozens of dependencies and then attempting to tree-shake dead paths out of the bundle. This craziness came up alongside the rise of javascript frameworks like the heavy but barebones react. With react it’s up to every developer to build a solid app fundament of documented, maintained libraries that provide modern, tree-shakable modules, whose code is not bloated, and whose APIs fit in.

Svelte is not yet as mature as the competition, with considerably lower adaption and without a real ecosystem. A few rough edges need to be ironed out, and svelte’s excellent documentation does not yet share best practices like vue’s (style guide). Maybe no one yet built applications large enough to have stumbled across patterns?

Behind the scenes, svelte takes the somewhat novel approach of compiling the application code and required framework code into plain javascript. Another technical choice diverging from players that have dominated the space across the last couple of years is that no virtual DOM is used. In practice, all of that comes with no noticeable downfall, but a few benefits:

Taken all together, svelte is comfortable for developers, while providing a good foundation to offer a great user experience. It’s nice to know that in svelte projects the DX won’t be as much at odds with the UX compared to a similar react project, where two handful of additional libraries are required to reach feature-parity with svelte.

Svelte is a language.

Some tinkering later, the statement that svelte is a language makes sense. Whether language or javascript framework, there’s plenty competition. I wish you the best of luck, svelte!

Footnotes

  1. Update, July 2020: There’s very rough approximations of the expected svelte bundle size with thirty components here.