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
- 118