DEV Community

Cover image for How to learn Svelte on a weekend
Gábor Soós
Gábor Soós

Posted on

How to learn Svelte on a weekend

I've been working with Vue and Angular for years, and recently I've tried React. I was surprised how easy it was to learn React compared to the previous ones. The straightforward answer would be that React is easier to learn, but I didn't feel that I had to learn fewer things. I thought maybe the way I've learned it made such a big difference. I needed another framework to validate my assumptions. The chosen was Svelte, the newest and smallest of them. I've gathered my thoughts on how to go through the building blocks with the same application. To my greatest surprise, it took me only two days to finish learning Svelte and building the application.

Instead of reading the documentation from start to finish or taking a long video course, I've decided to learn Svelte by building a basic but quite powerful application if appropriately implemented, the well-known TodoMVC application. Some of you may say that the TodoMVC is too simple for learning a framework: it can be right if you use a minimal toolset, but not right if you break it down to multiple components with state management and testing.

Learning a framework can be a time-consuming task. What worked for me to speed it up enormously is to break down the process into consumable chapters. Starting with the basic building blocks to more complex ones and reading the necessary parts from the official documentation was the right path. This way, it didn't feel overwhelming, and I always had a little success.

I'll walk you through the building blocks in the same order I've learned them. You can extend this list with extra items, but I think the ones I mention are necessary to understand how the framework works. You can check the final application in my Github repository. If you want to speed it up with one of the other big frameworks check out my other repositories and follow along the building blocks mentioned below (Vue, Vue Composition API, Angular, React, React Hooks)

Component

Components are the building blocks of Svelte applications. We'll start with them and later add state management. If you use the official template it'll give you a simple component out of the box.

Creation

Let's create a static component from scratch. Docs

<script>console.log('runs once');</script>
<style>.main { display: inline; }</style>
<div class="main">Hello World</div>
Enter fullscreen mode Exit fullscreen mode

Components reside in .svelte files, using a superset of HTML. The file contains three parts:

  • the script tag contains JavaScript that runs once when a component instance is created
  • the style tag contains CSS scoped to the component
  • all other regular HTML tags are considered to be part of the component's template

Display and bind

Now we have to make it dynamic by adding state and including it in the template. Docs

<script>
  let firstName = 'Chris';
  const countries = ['Canada'];
  let isVisible = true;
</script>
<style>.main { display: inline; }</style>
<div class:main={isVisible}>
  <h1>{firstName}</h1>
  <input name="first_name" value={firstName} />
  {#if isVisible}
    <ul>
      {#each countries as country }
        <li>{country.toUpperCase()}</li>
      {/each}
    </ul>
  {/if}
</div>
Enter fullscreen mode Exit fullscreen mode

Every locally declared block-scoped variable is available inside the component's template and can be displayed or assigned to an attribute with curly braces. Conditional statements and loops have a special syntax starting with {#. Expressions inside curly braces are treated as Javascript expressions.

Modify with interactions

The previous component was dynamic but didn't receive any input from the user. Adding event handlers is a way to make the component interactive. Docs

<script>
  let firstName = 'Chris';
  const modifyName = event => (firstName = event.target.value);
</script>
<div>
  <h1>{firstName}</h1>
  <input name="first_name" value={firstName} on:input={modifyName} />
</div>
Enter fullscreen mode Exit fullscreen mode

Locally declared variables become reactive by default. Reactivity means if we modify its value, it reacts to changes. Functions declared in the script tag can be used for event handling.

Parent-child interactions

If we don't intend to put everything into one big component, we'll have to break it down into multiple ones. The components will have a parent-child relationship and communicate through props and events. Docs

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  export let name;
  const modifyName = event => dispatch('modify', event.target.value);
</script>
<div>
  <h1>{name}</h1>
  <input name="first_name" value={name} on:input={modifyName} />
</div>
Enter fullscreen mode Exit fullscreen mode

The child component notifies the parent component by triggering an event with the function returned from createEventDispatcher. Declaring and exporting a variable enables the parent component to pass down property with the same name. This variable is reactive also: if the parent modifies the value of the property, it makes the variable change its value in the child component.

<script>
  import NameInput from './name-input.svelte';

  let firstName = 'Chris';
  const modifyFirstName = event => firstName = event.detail;
</script>
<div>
  <NameInput name={firstName} on:modify={modifyFirstName} />
</div>
Enter fullscreen mode Exit fullscreen mode

The parent component first has to import the child component and reference it with a camel-cased name inside its template. Props are passed down as attributes, and custom events are bound as native event listeners.

Lifecycle

Sometimes actions are needed at certain points of the lifecycle of the component like at mounting, updating, etc.

<script>
  import { onMount } from 'svelte';

  let firstName = 'Chris';
  onMount(() => console.log('mounted'));
</script>
<h1>{firstName}</h1>
Enter fullscreen mode Exit fullscreen mode

Callbacks passed as an argument to the methods starting with on are executed every time that lifecycle event happens.

State management

When multiple components use the same data and passing down props through multiple layers becomes tedious, a state management library can help solve the problem.

Store creation

Svelte has a built-in store implementation, no need to import an external library. Docs

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

export const name = writable('Chris');
export const upperCaseName = derived(name, current => current.toUpperCase());
Enter fullscreen mode Exit fullscreen mode

We can create a store with the writable method. Modified values from the state can be created with the derived getter. Whenever the original value changes, it calls the callback inside the derived function.

<script>
  import { name, upperCaseName } from './store';

  const modifyName = event => name.set(event.target.value);
</script>
<div>
  <h1>{$upperCaseName}</h1>
  <input name="first_name" value={$name} on:input={modifyName} />
</div>
Enter fullscreen mode Exit fullscreen mode

Store values display differ from locally declared variables in the $ prefix. Behind the scenes, Svelte calls the stores subscribe which runs on every value change.

Global access

It's important to access the store everywhere in the application. Directly referencing the store is a quick solution but introduces tight coupling between the store and the components. Let's look at how to access the store everywhere with loose coupling. Docs

<script>
  import { setContext } from 'svelte';
  import { name, upperCaseName } from './store';
  import NameInput from './name-input.svelte';

  setContext('name', name);
  setContext('upperCaseName', upperCaseName);
</script>
<div>
  <NameInput />
</div>
Enter fullscreen mode Exit fullscreen mode

The setContext method creates a context variable that is only accessible inside the component and its children.

<script>
  import { getContext } from 'svelte';

  const name = getContext('name');
  const upperCaseName = getContext('upperCaseName');

  const modifyName = event => name.set(event.target.value);
</script>
<div>
  <h1>{$upperCaseName}</h1>
  <input name="first_name" value={$name} on:input={modifyName} />
</div>
Enter fullscreen mode Exit fullscreen mode

The context variable (in our case the store) is accessible through the getContext method.

Testing

What else can guarantee the application is running as intended if not tests? Svelte has no recommended way of testing, we have to choose our own tools.

Unit

Unit tests are the ones we write for a unit in isolation (component, class, etc.). We can use the Jest testing framework for running the tests. Docs

<script>
  let firstName = 'Chris';
  const modifyName = event => (firstName = event.target.value);
</script>
<div>
  <h1 data-testid="display">{firstName}</h1>
  <input data-testid="input" value={firstName} on:input={modifyName} />
</div>
Enter fullscreen mode Exit fullscreen mode

We're reusing the component introduced earlier.

import { render } from '@testing-library/svelte';
import NameInput from './name-input.svelte';

describe('CopyRight', () => {
  it('should render component', () => {
    const { getByTestId } = render(NameInput);

    const inputElement = getByTestId('input');
    inputElement.value = 'Alexa';
    fireEvent.input(inputElement);

    expect(getByTestId('display')).toHaveTextContent('Alexa');
  });
});
Enter fullscreen mode Exit fullscreen mode

Test-cases are organized with the describe block and run by the it block. The Svelte Testing library aims to simplify everyday tasks connected to component testing.

End-to-End

End-to-end tests observe the application as a black-box, typically they are run in the browser. We can use the Cypress testing framework for this task. Docs

describe('Display Name', () => {
  it('it should change name', () => {
    cy.visit('/');

    cy.contains('h1', 'Chris');

    cy.get('input').type('Alexa');

    cy.contains('h1', 'Alexa');
  });
});
Enter fullscreen mode Exit fullscreen mode

The organization of the test-cases is the same with describe and it blocks. The difference is that we give commands with the global cy object.

Additional areas

The topics we've touched are enough for an entry-level application, but more complex ones need other areas like routing (docs), request handling (docs), and animations. My goal was to get an overall understanding of the framework, and I knowingly excluded them as they weren't necessary for it.

Summary

Learning frameworks can be challenging, but if we identify what is necessary to understand it, we can dramatically reduce the learning time. Creating a list out of what we have to learn makes it consumable and measurable. Not to mention, we always have a little success on the road. I hope it makes you try out a new framework that you have been thinking of.

Top comments (1)

Collapse
 
bhermans profile image
Bart Hermans

Awesome post, Gábor! I've been meaning to take a look at Svelte and you broke it down in small, easy to understand pieces. Thanks a lot for this great introduction!