Svelte was rated as the most loved web framework by developers in 2021 (link). So what is Svelte and why is it so loved?
Svelte is a rather unique javascript framework which aims to be 'truly reactive' and help developers 'write less code'.
It has the awesome feature of creating tiny code bundles by making the framework itself 'disappear' in a compilation stage, shipping optimised vanilla JavaScript code rather than using large and complex libraries loaded at run time.
In my opinion, this is the game changer that makes Svelte standout from other JavaScript frameworks. A tiny bundle size means a much faster load time, which seems to be the direction the web is taking as more and more data shows the benefits of a quick website. This compilation stage also removes the need for techniques such as the virtual DOM, used by React and Vue, further increasing the speed of a website.
Another feature is the absence of boilerplate. Svelte feels very close to standard web development, where components can look exactly like vanilla HTML. I’m sure this is a big reason why developers love this framework.
To introduce Svelte, let’s use the the poke api to create a simple single page app where users can select a pokemon, with a live search bar to filter through the list of all the pokemon. This will demonstrate all the core features of Svelte in a useful way. The full code can be found here
Table of Contents
- Installation
- Component Features
- Variables & Reactivity
- onMount & Async Fetching
- Reactive Declarations
- Loops
- Conditional Rendering
- Components & Props
- Custom Events
- Bind Forwarding
- Stores
- Final Notes
Installation
Let’s first install basic Svelte. To do this, run the following command
npx degit sveltejs/template new-svelte-project
This will copy the svelte starter template into your desired folder.
To enable typescript, go into your new svelte folder, and run
node scripts/setupTypeScript.js
Now all you need to do is install the necessary files by running
npm install
Component Features
A svelte component is a file ending with .svelte.
As you can see in App.svelte, a Svelte component can be pretty simple, containing three parts; html, a script tag to put your javascript, and a style tag to place css.
This is similar to Vue, just without the boilerplate code.
Clear the script and html contents of App.svelte, and let’s use the given css in the style tag.
Variables & Reactivity
Variables are created in the script tag.
We can create a string variable and display it in the DOM very easily by using curly braces {}.
<!-- For example -->
<script lang="ts">
let name: string = 'pokemon searcher'
</script>
<h1>{name}</h1>
To search through pokemon names, we’ll need an input field and a variable that holds the contents of that field.
To make the 'pokemonName' variable equal to the contents of an input field, we can use a special svelte keyword 'bind', which enables two way binding of the 'pokemonName' variable.
<!-- App.svelte -->
<script lang="ts">
let pokemonName: string = ''
</script>
<main>
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
<h1>Pokemon: {pokemonName}</h1>
</main>
Now, typing into the input field changes the output of the pokemon title.
This bind keyword enables two way binding without using an 'onInput' function that changes the value of the ‘pokemonName’ variable like in React.
onMount & Async Fetching
For this sample app, we store pokemon names from the pokeapi in a variable as an array of strings.
We want to fetch the data and map it as soon as the component is rendered.
For this, we can use 'onMount', which is a svelte lifecycle function that runs after the component is first rendered to the DOM. Let’s use this to fetch from the pokeapi and map it into an array of pokemon names.
<!-- App.svelte - script tag -->
<script lang="ts">
import { onMount } from 'svelte'
let pokemonName: string = ''
// Fetch from api then store the mapped names.
let pokemonData: string[] = []
onMount(() => {
const setPokemonData = async (): Promise<void> => {
const rawPokemonData = await (
await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')
).json()
pokemonData = rawPokemonData.results.map(
(p: { name: string; url: string }) => p.name
)
}
setPokemonData()
})
</script>
We now have a list of pokemon names in the 'pokemonData' array, which we can use in our simple project.
Reactive Declarations
For the live search feature, we need to have an array that holds the items filtered by the user input from the pokemon names.
Svelte has an awesome tool to deal with states that are derived from other properties, reactive declarations.
They look like this.
$: reactiveVar = otherVar * 2;
Now, 'reactiveVar' is a variable, but its value is computed every time the 'otherVar' variable changes (svelte runs the computation when the variables used in that computation changes).
We can make the variable that holds the filtered pokemon names into a reactive declaration. We’ll call this 'suggestions'.
<!-- App.svelte - bottom of script tag -->
<script>
// ...
let suggestions: string[]
$: suggestions =
pokemonName.length > 0
? pokemonData.filter(
(name) => name.includes(pokemonName)
)
: pokemonData
</script>
So, 'suggestions' is an array of pokemon names that includes the string entered in the input field.
The reactive assignment doesn’t work with typescript, so we can declare a 'suggestions' variable normally to preserve type checks.
Loops
We’ll want to display the contents of this 'suggestions' array on the page, and we can do this by using a svelte loop. Svelte has a special keyword 'each' that enables us to display DOM elements for each item in the given iterable.
To display each pokemon name, simply use the each keyword to loop over the 'pokemonData' variable.
<!-- App.svelte - html -->
<main>
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
{#each suggestions as suggestion}
<h2>{suggestion}</h2>
{/each}
</main>
As we type into the input field, we can see the list of suggestions change. Pretty cool for such simple code.
Conditional Rendering
Svelte has other keywords. Another useful one is #if.
The #if keyword allows for conditional logic.
For example, we can render a loading screen while we fetch the data from the pokeapi.
<!-- App.svelte - html -->
<main>
{#if pokemonData && pokemonData.length > 0}
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
{#each suggestions as suggestion}
<h2>{suggestion}</h2>
{/each}
{:else}
<h2>Loading...</h2>
{/if}
</main>
Components & Props
Props are used to pass data from one component to another. This is fairly standard for frameworks, and the syntax for this is very simple.
To signal that a component accepts a prop, an export keyword is used for a variable.
<script lang="ts">
export let stringProp: string
</script>
Now, to pass the value for 'stringProp', simply use the name of the exported variable when writing the component.
<script lang="ts">
import NewComponent from './NewComponent.svelte'
</script>
<NewComponent stringProp="prop value" />
For our app, let’s create a component for each suggestion.
Create a new file 'Suggestion.svelte' in src/, and simply accept and display a 'suggestion' prop.
<!-- Suggestion.svelte -->
<script lang="ts">
export let suggestion: string
</script>
<div class="suggestion">{suggestion}</div>
<style>
.suggestion {
font-size: 1.25rem;
}
</style>
Now, we can import this component and use it in our #each loop.
<!-- App.svelte - top of script tag -->
<script lang="ts">
import Suggestion from './Suggestion.svelte'
// ...
// ...
</script>
<!-- App.svelte - html -->
<main>
{#if pokemonData && pokemonData.length > 0}
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
{#each suggestions as suggestion}
<Suggestion suggestion="{suggestion}"/>
{/each}
{:else}
<h2>Loading...</h2>
{/if}
</main>
This is pretty pointless at the moment, so let’s add some logic to the 'Suggestion' component in the form of events.
Custom Events
Custom events can be dispatched from one component to another. This allows us to have parent-child communication.
For our app, we want to be able to click on a suggestion to select our pokemon. We can do this by dispatching a custom event from the 'Suggestion' component to the App component, and then setting the value of a variable which holds our chosen pokemon.
First, create the new 'chosenPokemon' variable, and display it on the screen in App.svelte.
<!-- App.svelte - bottom of script tag -->
<script lang="ts">
// ...
// ...
let chosenPokemon: string = ''
</script>
<!-- App.svelte - html -->
<main>
{#if pokemonData && pokemonData.length > 0}
<h1>Chose Your Pokemon</h1>
<h2>Chosen Pokemon: {chosenPokemon}</h2>
<div>
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
{#each suggestions as suggestion}
<Suggestion suggestion="{suggestion}"/>
{/each}
</div>
{:else}
<h2>Loading...</h2>
{/if}
</main>
Now, in Suggestion.svelte, we can dispatch a custom 'chosePokemon' event when clicking on a suggestion.
To create a custom event, we need to import the ‘createEventDispatcher’ from svelte.
<!-- Suggestion.svelte - script tag -->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let suggestion: string
// Function to dispatch a custom event.
const dispatch = createEventDispatcher()
const chosePokemon = (): void => {
dispatch('chosePokemon', {
pokemon: suggestion
})
}
</script>
We now have a 'chosePokemon' function that dispatches a custom 'chosePokemon' event to the parent component.
To call this function when clicking on a suggestion, we need to use the svelte 'on:click' event like this.
<!-- Suggestion.svelte - html -->
<div class="suggestion" on:click="{chosePokemon}">
{suggestion}
</div>
Back in the App.svelte file, we can handle this custom event by using the 'on:(event name)' syntax.
<!-- App.svelte - 'Suggestion' component in html -->
<Suggestion
suggestion="{suggestion}"
on:chosePokemon="{(e) => {
chosenPokemon = e.detail.pokemon
}}"
/>
This handler sets the value of the chosenPokemon variable to be equal to the pokemon name passed in the custom event (located in the 'detail' property).
When we click on a suggestion, that pokemon name is shown.
I've set the 'chosenPokemon' variable this way to introduce custom events, however, there is a much cleaner and easier way of doing this: bind forwarding.
Bind Forwarding
We saw how the bind keyword was used to set up two way binding when creating an input field, but this keyword can also be used across our components.
In App.svelte, we can replace the chosePokemon event handler with a bind keyword on a chosenPokemon prop.
<!-- App.svelte - 'Suggestion' component in html -->
<Suggestion suggestion="{suggestion}" bind:chosenPokemon />
And in the 'Suggestion' component we can accept this prop and make the 'on:click' function simply set this 'chosenPokemon' variable.
<!-- Suggestion.svelte -->
<script lang="ts">
export let suggestion: string
export let chosenPokemon: string = ''
</script>
<div
class="suggestion"
on:click="{() => chosenPokemon = suggestion}"
>
{suggestion}
</div>
We now have the same functionality as before using a fraction of the code.
Stores
I want to wrap things up by introducing stores.
With Svelte, we don’t need to use an external library like Redux to have a central store, it comes with the framework.
Fundamentally, a store is an object with a subscribe method that allows our Svelte components to be notified of any changes to the store value. Svelte defines 2 different types of stores, a writable store, and a readable store. As the names suggest, a writable store allows reads and writes, while a readable store only allows a read.
For our app, we’ll send the 'pokemonData' variable into the store. Since this variable simply gathers and stores the pokemon data, we’ll use a readable store.
First, we need a new file in the src folder (I’ll name it ‘stores.ts’).
We can import the readable store function from svelte, along with the required types.
// stores.ts
import { readable, Readable, Subscriber } from 'svelte/store'
A readable function, as its first argument, takes the store’s initial value, and, as its second argument, a 'start' function.
This 'start' function is called when the store gets its first subscriber, so it is where we’ll retrieve our api data.
The function receives a 'set' callback function, which is used to set the value of the store, and returns a 'stop' function which is called when the last subscriber unsubscribes (where we can perform some cleanup).
In our store, we can simply copy the contents of our 'setPokemonData' function, but instead of assigning the value of 'pokemonData', we call the 'set' function.
import { readable, Readable, Subscriber } from 'svelte/store'
const setPokemonData =
async (set: Subscriber<string[]>): Promise<void> => {
const rawPokemonData = await (
await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')
).json()
set(
rawPokemonData.results.map(
(p: { name: string; url: string }) => p.name
)
)
}
// Export the new store 'pokemonData' variable.
export const pokemonData: Readable<string[]> =
readable([], (set) => {
setPokemonData(set)
return () => set([])
})
That’s it. We now have a central store holding our pokemon names in 'pokemonData'.
To use our store, we need to import the 'pokemonData' variable from our store file.
We can then use the special svelte '$' symbol to reference the store's value.
<!-- App.svelte -->
<script lang="ts">
import { pokemonData } from './stores.js'
import Suggestion from './Suggestion.svelte'
let pokemonName: string = ''
let suggestions: string[]
// $pokemonData instead of pokemonData
$: suggestions =
pokemonName.length > 0
? $pokemonData.filter((name) =>
name.includes(pokemonName)
)
: $pokemonData
let chosenPokemon: string = ''
</script>
<main>
{#if $pokemonData && $pokemonData.length > 0}
<h1>Chose Your Pokemon</h1>
<h2>Chosen Pokemon: {chosenPokemon}</h2>
<div>
<span>Search: </span>
<input type="text" bind:value="{pokemonName}" />
{#each suggestions as suggestion}
<Suggestion
suggestion="{suggestion}"
bind:chosenPokemon
/>
{/each}
</div>
{:else}
<h2>Loading...</h2>
{/if}
</main>
Our app works the same, but our api data is now centrally stored and can be used in any component.
Now, while svelte has writable and readable stores, anything that sticks to the svelte store 'contract' and implements the subscribe method is a store.
This means that stores are very flexible and can be tailored to your needs. You can even create a store in another language, like Rust, as shown here.
Final Notes
Svelte stands out in the cluttered world of JavaScript frameworks by not compromising user experience for developer experience or vice versa.
Svelte provides fantastic tools to make developing apps easy, while it’s compiler results in a tiny package for end users, reducing download times significantly.
Svelte has been among the most loved frameworks for a while now, even as its usage grows, a potential sign that it will become as big as Vue or React. This coincides with the recent push towards a more performant web, moving away from crazy large JavaScript packages served to the client towards server-side or hybrid rendering.
The Svelte team is now working on SvelteKit, which is Svelte’s version of Next.js, which you can learn about here.
If you enjoyed this article, please consider sharing it.
Checkout my github and other articles.
Top comments (26)
While evaluating Vue.js replacements I tried Svelte a year ago and TypeScript support was lacking. There also wasn't a solid alternative for a nuxt.js/next.js like integrated SSR solution. Going all in on React with Next.js I cannot see myself going back to Svelte. React even though not perfect has an incredible community force behind it that makes finding libraries or modules a walk in the park. It looks Svelte today is much better at TypeScript support especially in Template literals, but for me the train has left the station. For Svelte to gain more traction SvelteKit needs to be a much higher priority.
Sometimes Next.js seems to be adopted like it's some kind of silver bullet.
As demonstrated by Optimizing Core Web Vitals on a Next.js app it can take quite a bit of effort to (in this case) nudge the Time to Interactive from "moderate" to "good" with a Next.js application. Next.js's Achilles heal is that it performs full page hydration since most React applications do full page renders on the client side. Back in 2019 an experimental, heavily customized Preact/Next.js application demonstrated the benefits progressive hydration could bring (see also Partial Hydration in Preact).
Based on this Remix is gunning for Next.js with their interpretation of progressive enhancement while Astro does it with partial hydration.
experiences. Web. frameworks. future. me. puts forward an interesting perspective. React (Next.js) and Svelte (SvelteKit) are Generation 2 Web solutions (Gen 2) - but the challenges of client-side hydration limits the effectiveness of their respective SSR solutions.
Meanwhile there are already early indications of Gen 3 - JavaScript-based server-first architectures. Server-first differs from traditional dynamic server rendering as there still can be a significant client side capabilities (e.g. nested routes)—but the client no longer dictates key aspects of the server-client dynamic; while both are separate their interactions will be more tightly coupled (compared to SPA + API) to deliver a better user experience. Gen 3 MPAs will attempt to optimize for FCP and TTI to provide universally "fast page navigation"—with may even leverage "streaming HTML".
The weirdly obscure art of Streamed HTML
Taylor Hunt ・ Mar 15 '22 ・ 12 min read
Why Efficient Hydration in JavaScript Frameworks is so Challenging
Ryan Carniato for This is Learning ・ Feb 3 '22 ・ 9 min read
Thanks for the pointers. I agree it isn't a silver bullet and these frameworks introduce their own problems. It's a delicate tradeoff for me between a slower less flexible solution like Next.js or run a bleeding edge less common setup. As an entrepreneur I also have to consider the cost of onboarding new developers , or the impact of a more obscure framework when I want to sell my business.
It's hard to argue with that reasoning but I think the mindset has gotten to the point where it is self perpetuating SPA/React/Next.js adoption—products are built with React because of developer availability and developers adopt React because of demand. Adoption based on popularity of authoring experience rather than suitability towards the product being developed (Responsible JavaScript refers to this as the bandwagon fallacy).
The typical developer framework discussion revolves around a framework's impact on the developer('s happiness and productivity) rather than the impact on the end user. That facet gets highlighted elsewhere:
The Cost of Javascript Frameworks:
"Good frameworks should provide a better starting point on the essentials (security, accessibility, performance) or have built-in constraints that make it harder to ship something that violates those.
That doesn’t appear to be happening with performance (nor with accessibility, apparently)."
The Three Unattractive Pillars of Web Dev: accessibility, security and performance;
Responsible JavaScript:
"Frameworks don’t doom us to build shitty websites but to use them is to accept a certain amount of overhead you can never optimize away. You must step lightly, particularly since the ecosystem of installable plugins and off-the-shelf components available to such architectures can further compound their user-experience problems."
Your experience with Vue is unfortunate, especially if it lead to wasted effort. But disruptions like this aren't limited to the "smaller" frameworks; both Angular and React have had them:
Clearly it must be the pragmatic choice at times, it just seems that ecosystem/hire-ability considerations have made React the incumbent technology—despite the clear need for alternatives with different sets of priorities.
Thats fair. Next.js is really coming out ahead in the SSR world, and SvelteKit really needs a bit more work.
I wonder why you were looking for a vue replacement.. I'd appreciate it if you could share why....
This was at the time Vue 3 was introduced. My codebase was build using (TypeScript) Vue Class Components and there was no migration path going forward. Vuex wasn't well suited for TypeScript and it didn't look like this was going to change anytime soon. The Vscode extension Vetur and TypeScript templates didn't play well together, the overal dev experience was lacking. I think Vue is an awesome project and my move to TypeScript was probably what made it less enjoyable.
I think this post misses a very important aspect: the ecosystem. We have seen to many frameworks come and go, some are even sick of it. The ones who "survived" were react, vuejs and angular. Each has its own flavor, its pros and cons, but most of all they have a mature ecosystem, which svelte lacks of.
...interestingly enough, I used a lot its predecessor: ractive.js.org/ . It was made by the same guy, who then abandonned it in favor of svelte. Afterwards, ractive never really recovered from this blow. The community was rather small and deteriorated into a slow death. So, I got burned a bit. It makes me warry of web technologies lacking crucial momentum.
A solid point, especially in a professional setting a framework's ecosystem is a very important consideration.
A thriving ecosystem takes time however, and I do think that for Svelte it is steadily building up, with more more developers learning the framework and more tools being built.
I love Svelte, but I keep coming back to React/Next.js as many has pointed out already due to the eco-system and solutions being available. Its just a time thing perhaps and its great that Rich joined Vercel to work on it full-time and I hope to see it flourish!
On the other hand, the critique of elegant JS and the simplicity is a bit misguided I would say. Svelte has more "magic" behind the scenes including the state management and what is really happening behind the scenes of
bind:
Obviously more explicit code requires more writing and more lines but it also makes it a bit more logical.
Everything is sort of a trade-off. I am excited for Svelte though and its future!
If you are searching for true reactivity, try Solid.js – where svelte tries to get the most out of its compiler, Solid gets the most out of its reactive system, from state down to specific DOM updates.
Thanks for the suggestion. I'm always looking for more tech to try.
I like Svelte and it has pushed things forward, but it still has a lot further to go. I found testing hard with Svelte and Jest and Vite, I wrote about this recently:
Testing a Svelte app with Jest
Rob OLeary ・ Nov 18 '21 ・ 15 min read
As SvelteKit uses Vite, Vitest will probably be the way forward. However, it is the VueJS folks who are working on it, so integration with Vue is superior I guess...
Vitest doesn't prioritise Vue at all. Give it a try before bashing it 🙊
What I said in no way bashed Vitest. Quite the opposite, I said it will probably be first choice for testing apps that use vite because it has support of some vite/VueJS folks and that it looks promising. I haven't tried it, but I intend to. I was merely speculating on how far along it is wrt support for different frameworks. Try to find the good in what people say
I looked at Svelte before and I decided against it because of the imperative programming paradigm it uses. React allows me to write (mostly) functional typescript/javascript, and there are other frameworks too.
Maybe it's possible and I've just not looked at it well enough.
A very good point in favor of React for me. I think functional style programming with React is very beautiful.
I have looked into doing the same with Svelte and it is possible using immutable stores, but I need to take a further look to judge how good it works.
I have made few small games and app with Svelte in my internship and they were so fun to build. I feel for small and medium size projects, Svelte is a very good solution. Only thing I will complain about Svelte is the CSS scope. I was creating a component library with it and it was painful to override the styles that the components came with.
I really don't see any reason to switch from Vue 3 to Svelte.
That's awesome. Switching is time consuming and may not yield the benefits touted by the evangelists.
you can say that about any frameworks without opinion
Is it posible to make ssr with svelte and .net core?
Should i use sveltekit or just svelte for that?
I believe both options can be utilised. Here a thread with some ideas: reddit.com/r/sveltejs/comments/r4x...
Better still, try RiotJS