Building with SvelteKit

Connect.Tech 9th November 2022

SCOTT SPENCE

Developer Relations Engineer

and GraphQL

!workshop

Scott Spence

  • Developer Relations Engineer @Storyblok
  • Svelte LDN meetup organiser
  • Workshop host
    • ​Jamstack Conf
    • GraphQL Galaxy
    • JSNation
    • Jamstack Explorers
  • Cat dad 😺
  • Human dad

I’m from the UK

I’m from the UK

Fries = Chips

Chips = Crisps

April 2021

I've never Svelte like this before!

— Scott Spence

Svelte.

SvelteKit.

What is Svelte?

It is an actual word

It is an actual word

It’s a framework?

v1 Release late 2016

v3 April 2019

Rethinking Reactivity

You Gotta Love Frontend

What is a framework?

Declarative code?

Declarative code

Imperative code

vs

Imperative

function component() {
  let count = 0;

  const button = document.createElement('button');
  button.textContent = `Clicks: ${count}`;

  button.addEventListener('click', () => {
    count += 1;
    button.textContent = `Clicks: ${count}`;
});
  return button;
}

Declarative

<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>
import React, { useState } from 'react';

export default function Example() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>
    Clicks {count}
  </button>;
}
import React, { useState } from 'react';

export default function Example() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>Clicks {count}</button>;
}
<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>Clicks: {count}</button>

How is Svelte different?

It’s a compiler

What does that mean?

Just ship what is needed

Just ship what is needed

Compiler-centric design benefits:

  • JavaScript is byte for byte the most expensive thing on the web. Shipping less === faster app stat up
  • No need to re-generate a virtual DOM on every state change. Instead, Svelte makes surgical, granular updates as it knows what needs to change
  • Your apps will generally be smaller, as there's no need for a framework runtime

Write less code

Write less code

Modern web development best practices out of the box:

  • Built in element transitions and animations, no need for third party packages
  • Manage cross-component state without the need for third party state management libraries
  • If you’re not using a specific feature like animations, transitions or bindings then these are not in the compiled output

Single file component

Single file component

<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>

Single file component

<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>

<style>
  button {
    background-color: #4CAF50;
    border: none;
    color: white;
    padding: 20px;
    font-size: 16px;
    cursor: pointer;
    border-radius: 12px;
  }
</style>

Single file component

<script>
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>

<style>
</style>

Single file component

<script>
  let count = 0;
</script>

<button
  class="btn btn-primary"
  on:click={() => (count += 1)}
>
  Clicks: {count}
</button>

Reactivity in Svelte

<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>
<script>
  let count = 0;
  $: doubled = count * 2;
</script>

<button on:click={() => count += 1}>
  Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>

$: WTF??!!1

Gotchas?

<script>
  let numbers = [1, 2, 3, 4];

  function addNumber() {
    numbers.push(numbers.length + 1);
  }

  $: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}>
  Add a number
</button>
<script>
  let numbers = [1, 2, 3, 4];

  function addNumber() {
    numbers = [...numbers, numbers.length + 1];
  }

  $: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}>
  Add a number
</button>

Stores

Stores

<script>
  import { writable } from 'svelte/store';

  const count = writable(0);
  console.log($count); // logs 0

  count.set(1);
  console.log($count); // logs 1

  $count = 2;
  console.log($count); // logs 2
</script>

HTML

HTML Superset

The platform provides!

SvelteKit.

What is SvelteKit?

Svelte is the UI layer

SvelteKit is the meta-framework

SvelteKit

  • Full stack

  • HTTP endpoints

  • File based routing

  • SSR

  • Code splitting

Data Loading in SvelteKit

File based routing in SvelteKit

sveltekit-skeleton-example/
├─ src/
│  ├─ routes/
│  │  └─ about/
│  │  │  └─ index.svelte
│  │  └─ blog/
│  │  │  └─ index.svelte
│  │  └─ contact/
│  │  │  └─ index.svelte
│  │  └─ index.svelte
│  └─ app.html
└─ package.json
sveltekit-skeleton-example/
├─ src/
│  ├─ routes/
│  │  └─ about/
│  │  │  └─ +page.svelte
│  │  └─ blog/
│  │  │  └─ +page.svelte
│  │  └─ contact/
│  │  │  └─ +page.svelte
│  │  └─ +page.svelte
│  └─ app.html
└─ package.json
sveltekit-skeleton-example/
├─ src/
│  ├─ routes/
│  │  └─ about.svelte
│  │  └─ blog.svelte
│  │  └─ contact.svelte
│  │  └─ index.svelte
│  └─ app.html
└─ package.json

Example time 🧑‍💻

Why Svelte?

Awesome DX

(Developer Experience)

Businesses don't really care about “Developer Experience”

The business does care about outcomes.

Outcomes are the business experience.

Svelte is more succinct (clear, precise) than other frameworks

Improved parsing of the code by other developers

Lead times reduced by 40%

Bug fixes are reduced

But the business doesn't care about any of that

Solving bugs faster = reduced lead time == saving budget

Businesses get that

It’s smaller

It’s smaller

  • Speed

    • Smaller bundle
  • Devices

    • Works on lower quality devices
  • Performance

    • Faster time to interactive

How users interact with your site

and it will impact rankings

Clickthrough Rates

Time spent on page

Return rates

Bounce Rates & Conversions

where it really matters

💷 💳 💸 🤑

How fast is fast enough?

2 seconds ⏱

Performance Matters

GraphQL

  • Simple to understand

  • You get what you ask for

  • Client (browser) or server side

query {
  products {
    # from Storyblok
    name
    description {
      richtext
    }
    # from 3rd party API
    inventory {
      inStock
    }
    # rest of query
  }
}

Client side

(the browser)

URQL

<script>
  import { createClient, setContextClient } from '@urql/svelte'
  import '../global.css'

  const client = createClient({
    url: `https://rickandmortyapi.com/graphql`,
  })
  setContextClient(client)
</script>

<main class="container">
  <slot />
</main>

<style>
  /* layout styles go here */
</style>

src/routes/+layout.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

<style>
 /* page styles go here */
</style>

src/routes/+page.svelte

src/routes/+page.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>
<pre>{JSON.stringify($charactersQueryStore, null, 2)}</pre>

SvelteKit debug 👀

{
  "characters": {
    "results": [
      {
        "name": "Rick Sanchez",
        "id": "1",
        "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg",
        "__typename": "Character"
      },
      {
        "name": "Morty Smith",
        "id": "2",
        "image": "https://rickandmortyapi.com/api/character/avatar/2.jpeg",
        "__typename": "Character"
      },
      {
        "name": "Summer Smith",
        "id": "3",
        "image": "https://rickandmortyapi.com/api/character/avatar/3.jpeg",
        "__typename": "Character"
      },
    ],
    "__typename": "Characters"
  }
}

src/routes/+page.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

<style>
 /* page styles go here */
</style>

src/routes/+page.svelte

src/routes/+page.svelte

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

Closing Comments

  • Great client side tools to use

  • Awesome developer experience (DX)

  • Decrease in lead time for development

  • Small bundle sizes

  • Increased performance

Svelte Content Creators

  • Geoff Rich
  • LevelUpTuts (Scott Tolinski)
  • Tan Li Hau (Li Hau - hello, hello)
  • Joy Of Code (Matia)
  • Huntabyte (Hunter Johnston)

Thanks 🙏