Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Svelte, I kept hearing the name more and more.
It's supposed to change everything
For real I said, another SPA framework?
Yea yea, this one is IT. It might topple one of the big three Angular, React, Svelte.
Of course, I'm a bit doubtful. Sure it might pick up over time or are we already there?
Let's see :)
So what would make us throw out the framework we currently work with or add to our toolbelt?
Well, one of the first things I do is to look at GitHub and see how popular is this thing?
Let's see ~30k starts, used by 9.5k. That's quite respectable I guess.
Did this thing come out of nothing?
Well doing some research shows it was created in 2016 and is currently on version 3. So it's been around, that's good.
Let's say we choose Svelte for our next project though, just to try things out. What should we expect from it to take it seriously?
Well, this is MY must-have list, your list might be different:
- Component centered, I mean all the great frameworks today are component centric
- Routing, yes I need routing
- Testing, I'm not gonna write a bunch of code without a testing library
- Forms, as boring as forms sounds, yes there needs to be decent support for collecting data to a form.
- Data binding, some kind of data binding is what we want
- Tooling, I expect there to be a CLI so that I can scaffold a project, run my app, even better if there's hot reloading. I additionally want there to be an easy way to bundle my app
Ok, we got a list of requirements/features that we want to investigate. But first, let's talk about how Svelte does things.
WHAT
Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app.
Ok, so a lot of things happen on compile. What else can you tell me?
Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.
Ok, so NO virtual DOM. But surgical updates, sounds exotic.
Svelte is a component framework, like React, Vue and Angular.
Ok good, we got components
There is a difference though. The mentioned frameworks use declarative state-driven code that needs to be converted into DOM operations. This comes with a cost on framerate and garbage collection.
and I guess Svelte avoids that somehow?
Svelte is different, Svelte runs at build time. Their components are turned into imperative code which gives it excellent performance.
sounds promising
Svelte is currently on version 3 has gone through significant change to ensure the developer experience is great and purged of most of the boilerplate code.
Developer experience, yup got to have that.
Resources
Here are some resources I think you should check out at some point, either while reading this or after.
https://svelte.dev/
The official resource site that includes an interactive tutorial that I highly recommendhttps://svelte.dev/blog/svelte-3-rethinking-reactivity
The blog post that announced the arrival of Svelte.https://svelte.dev/blog/virtual-dom-is-pure-overhead
On the Virtual DOM. This pretty much outlines why the Virtual DOM doesn't come for free, has limitations etc.https://svelte.dev/blog/setting-up-your-editor
This talks about how to set up your IDE to have it recognize Svelte files but also how to install extensions that support auto-completion and more. Extensions are available for VS Code and Vim.https://dev.to/vintharas/ discovering-svelte-getting-started-with-svelte-writing-a-pomodoro-technique-app-2lph
Great article by Jaime covering Svelte and how to actually build something with it.
Component
Svelte is like the three big SPAs, Vue, React, Angular, component-oriented. So let's talk about components in Svelte.
A component in Svelte is stored in a separate file with the file ending with .svelte
. It has a script
part, containing your code, a style
part for your styles and a markup part.
A simple component can look like this:
<script>
let name = 'world';
</script>
<h1>Hello {name}</h1>
That's it?
Yea, not much at all. However, looking at the resulting code this tells a different story:
/* App.svelte generated by Svelte v3.16.7 */
import {
SvelteComponent,
detach,
element,
init,
insert,
noop,
safe_not_equal
} from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Hello world!";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
}
};
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default App;
That's a lot. The good news is that we DON'T have to write the above.
Interpolation
Note how we use interpolation with {}
.
This can be used on HTML attributes as well, like so:
<script>
let src = 'tutorial/image.gif';
</script>
<img src={src}>
Styling
Additionally to placing our code in a script
tag - we place our styles in a style
tag, like so:
<style>
p {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
and the best part is that it's scoped to the component - it won't leak out.
Importing a component
You import a component by using the import
keyword like so:
<script>
import Nested from './Nested.svelte';
</script>
and use it like so:
// App.svelte
<script>
import Nested from './Nested.svelte';
</script>
<p>Some text</p>
<Nested />
Wasn't that easy? You barely see that there's a framework there, just HTML, CSS and JS.
Your first Project
Enough of all this theory. Let's get started and build something. The easiest way to build anything with Svelte is to scaffold out a new Svelte project using the following command:
npx degit sveltejs/template <name of project>
Thereafter run:
npm install
followed by
npm run dev
and you should see the following:
We seem to have LiveReload
, nice!.
It's up and running on port 5000
. Let's check it out!
There we have it. Hello Svelte.
What about that Live Reloading? We should be able to go into our code and change a variable and see it reflected in the Browser with no start/stop of the app.
and the browser now shows:
Great. That works. Yea I feel a little spoiled wanting live reload to work. I remember starting out with JS and not having this.
Good thing it's a must nowadays :)
Building our first component
Ok, we have a project, let's keep working with it by creating our first component and learn a few tricks like how to render data and how to work with properties or props as they are called.
Let's create a CV component by creating the file CV.svelte
and give it the following content:
<script>
let title = 'chris'
</script>
<h1>{title}</h1>
Now open up App.svelte
cause we need to use this component by :
- Import, we need to import the component to be able to use it
- Add it to the markup
You need the following row for the import, place it within the script
tag:
import CV from './CV.svelte';
To use it we need to place it in the markup like so:
<main>
<CV />
</main>
You should now see this in the browser:
Props
Next we want to learn how to send data to our component. We do that using properties or props as they are called in Svelte. So how to use them?
Simple, use the keyword export
.
Go back to your CV.svelte
file and add the keyword export
like so:
<script>
export let title = 'chris'
</script>
Now we can actively set title
property from the outside. Let's open up our App.svelte
file and do just that.
We define a new object in the script
section:
let person = {
name: 'chris'
}
Then we refer to it in the markup section like so:
<main>
<CV title={person.name} />
</main>
That still seems to work in our browser, great :)
Using for-loop
Of course we want to be able to render more complex data than a string or number. How bout a list? We can easily do that by using a construct that looks like so:
{#each skills as skill}
<div>Name: {skill.name}, Level: {skill.level}</div>
{/each}
skills
above is a list and skill
is the name we give a specific item on the list. We need to do the following to get all this to work:
- Update our person object to contain a list of skills
- Change our input property to take an object
- Add for-loop rendering code to our CV component
Let's start with App.svelte
and update our data object to look like so:
let person = {
name: 'chris',
skills: [
{
name: 'Svelte',
level: 5
},
{
name: 'JavaScript',
level: 5
}
]
}
Now let's send the entire object instead of just the title. So we change the markup in App.svelte
to:
<main>
<CV person={person} />
</main>
Now we open up CV.svelte
and we change it to the following:
<script>
export let person;
</script>
<h1>{person.name}</h1>
{#each person.skills as skill}
<div>Skill: {skill.name} Level: {skill.level}</div>
{/each}
this should now look like this:
Using conditionals
Ok, it's looking better but we should learn how to use IF, ELSE and those kinds of statements. Let's work on the skills
data and render it differently depending on the level.
Let's say we want to output REALLY GOOD
if the level is on 5
and GOOD
if level is on 4. We can solve that using the conditional constructs in Svelte that looks like so:
{#if condition}
// render something
{:else if otherCondition}
// render something else
{:else}
// render
{/if}
Logic
We can use template logic to express IF and FOR-loops like so
IF
{#if condition}
// markup
{/if}
An example of this is the following login component:
<script>
let user = { loggedIn: false };
function toggle() {
user.loggedIn = !user.loggedIn;
}
</script>
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}
{#if !user.loggedIn}
<button on:click={toggle}>
Log in
</button>
{/if}
ELSE
We can improve the above by using ELSE. The syntax for that is {:else}
inside of a {#if}
. Here's an example:
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}
ELSE IF
We can additionally use ELSE IF to express even more boolean switch logic. Just like ELSE it's using a :
like so {:else if condition}
. A longer example looks like so:
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}
Let's add an entry to our skills
list { name: 'Photoshop', level: 3 }
and adjust our component CV.svelte
to look like this:
<script>
export let person;
</script>
<h1>{person.name}</h1>
{#each person.skills as skill}
<div>Skill: {skill.name}
Level: {skill.level}
{#if skill.level == 5}
REALLY GOOD
{:else if skill.level == 4}
GOOD
{:else}
DECENT
{/if}
</div>
{/each}
Ok, good, we know how to work with conditionals as well.
Adding HTTP
One really cool thing in Svelte is how easy it is to work with HTTP endpoints and render the result. For this, we will use a template construct called await
.
Let's talk to one of my favorite endpoints SWAPI, the Star Wars API. To be able to use our await
construct we need to go about it in the following way:
- Construct our Promise, this is where we make the actual call to our endpoint
- Define our async template, Here we will set up the markup so that we can render the data when it arrives but also so we have the capability to render if something goes wrong
Construct our Promise
Let's define a function in our component like so:
<script>
let promise = getData();
async function getData() {
const response = await fetch('https://swapi.co/api/people');
const json = await response.json();
return json.results;
}
</script>
Define our async template
The template for it looks like so:
{#await promise}
<p>...loading</p>
{:then data}
<p>Here is your data {data}</p>
{#each data as row}
<div>{row.name}</div>
{/each}
{:catch error}
<p>Something went wrong {error.message}</p>
{/await}
As you can see above we have pointed out our promise
variable as the thing to wait for. We have also specified {:then data}
as where our fetched data should be rendered and that we also give that data the name data
. Finally, we specify where we render any errors with {:catch error}
.
Let's add all of this to a separate component HttpDemo.svelte
and have it look like so:
<!-- HttpDemo.svelte -->
<script>
let promise = getData();
async function getData() {
const response = await fetch('https://swapi.co/api/people');
const json = await response.json();
return json.results;
}
</script>
<style>
.row {
margin: 10px;
box-shadow: 0 0 5px gray;
padding: 10px 20px;
}
.error {
background: lightcoral;
border: solid 1px red;
padding: 10px 20px;
}
</style>
{#await promise}
<p>...loading</p>
{:then data}
<div>
{#each data as row}
<div class="row">{row.name}</div>
{/each}
</div>
{:catch error}
<div class="error">
Something went wrong {error.message}
</div>
{/await}
Running the app you should have something looking like so:
Events
Ok, now we know a bit more how to work with different directives, how to render out data, work with HTTP and so on. What about events? Well, there are two types of events that are interesting to us:
- DOM events, these are typically when we click a button, move a mouse, scroll and so on. We can assign a handler to those events
- Custom events, these are events that we create and can dispatch. Just like with DOM events we can have handlers capturing these events.
So how do we learn these event types in the context of our app? Let's try to make our CV better by allowing data to be added to it.
Adding a skill
Ok, to be able to add a skill we need two things
- Input fields, these should capture the name of the skill and your current level in it
- A button, this should raise an event that ends up saving the skill to the CV
- Broadcast, we need to tell our component that a new skill has been added. After all the parent component is the one sitting on the data for the CV so it's there that we need to do our change
Input fields
Let's add the following markup
<h1>{person.name}</h1>
<h2>Add skill</h2>
<div>
<input bind:value={newSkill} placeholder="skill name">
<input bind:value={newSkillLevel} type="number" min="1" max="5" />
<button on:click={saveSkill} >Save</button>
</div>
A button
Now we need to add the following code in the script
section:
let newSkill = '';
let newSkillLevel = 1;
function saveSkill() {
// TODO save skill
console.log('saving skill', newSkill, newSkillLevel);
}
Broadcast
Now we need to implement the method saveSkill()
. It needs to raise a custom event that the parent component can listen to. We raise custom events in Svelte using createEventDispatcher
like so:
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
Let's apply that to our current code:
<script>
import { createEventDispatcher } from 'svelte';
export let person;
const dispatch = createEventDispatcher();
let newSkill = '';
let newSkillLevel = 1;
function saveSkill() {
dispatch('newSkill', {
skill: newSkill,
level: newSkillLevel
});
}
</script>
Ok we know how to send a message upwards, how do we capture it?
Simple, we use the on:<nameOfCustomMessage>
and assign a handler to it. Now open up the App.svelte
and let's add the following code to our markup and script section:
<CV person={person} on:newSkill={handleNewSkill} />
and for our script
section:
function handleNewSkill(newSkill) {
console.log('new skill', newSkill);
}
When running this you should get the following in the console:
Note above how our message is in the detail
property.
Let's finish up the code so we assign our new skill to our person
property and ensure that are UI works as intended.
function handleNewSkill(newSkill) {
const { detail: { skill, level } } = newSkill;
person.skills = [...person.skills, { name: skill, level }];
}
and our UI looks like:
Summary
I thought I would stop here. This article is already long enough. I plan many more parts on Svelte and this is how much I think you can digest in one go. In the next part let's how to work with routing and tests, cause we have those as well.