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 🙏

Building with SvelteKit and GraphQL
By Scott Spence
Building with SvelteKit and GraphQL
- 152