How YOU can learn to use Svelte for your next JS project

softchris

Chris Noring

Posted on January 13, 2020

How YOU can learn to use Svelte for your next JS project

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.

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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}>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

and use it like so:

// App.svelte

<script>
  import Nested from './Nested.svelte';
</script>

<p>Some text</p>
<Nested />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Thereafter run:

npm install
Enter fullscreen mode Exit fullscreen mode

followed by

npm run dev
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

To use it we need to place it in the markup like so:

<main>
  <CV />
</main>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
}
Enter fullscreen mode Exit fullscreen mode

Then we refer to it in the markup section like so:

<main>
  <CV title={person.name} />
</main>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Update our person object to contain a list of skills
  2. Change our input property to take an object
  3. 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
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

 Logic

We can use template logic to express IF and FOR-loops like so

IF

{#if condition}
// markup
{/if}
Enter fullscreen mode Exit fullscreen mode

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}

Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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
  2. 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

  1. Input fields, these should capture the name of the skill and your current level in it
  2. A button, this should raise an event that ends up saving the skill to the CV
  3. 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>
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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!'
  });
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

and for our script section:

function handleNewSkill(newSkill) {
  console.log('new skill', newSkill);
}
Enter fullscreen mode Exit fullscreen mode

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 }];
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
softchris
Chris Noring

Posted on January 13, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related