Nils Röhrig | JavaScript & Angular Days Berlin 2024
Coffee Break
10:30 - 11:00
Lunch Break
12:30- 13:30
Coffee Break
15:00 - 15:30
Begin
09:00
End
17:00
What is Svelte?
Svelte is a UI framework that uses a compiler to let you write breathtakingly concise components that do minimal work in the browser, using languages you already know — HTML, CSS and JavaScript. It’s a love letter to web development.
Logo
Register
Home
E-Mail
Submit
etc. pp...
Component
Component
Component
<h1>Simple Component</h1>
Component.svelte
<h1>Simple Component</h1>
<h1>Simple Component</h1>
<h1>Simple Component</h1>
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 = "Simple Component";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
},
};
}
class SimpleComponent extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default SimpleComponent;
import * as $ from "svelte/internal/client";
var root = $.template(`<h1>Simple Component</h1>`);
export default function Component($$anchor) {
var h1 = root();
$.append($$anchor, h1);
}
Component.js
<h1>Simple Component</h1>
import { mount } from "svelte";
import Component from "./Component.js";
mount(Component, {
target: document.querySelector("#root"),
props: { some: "prop" }
});
main.js
Svelte Component Basics
<script>
// STATE & BEHAVIOR
</script>
<!-- DECLARATIVE MARKUP -->
<h1>Hello Workshop People!</h1>
<p>{"Any expression works here!"}</p>
<style>
/* PRESENTATION */
</style>
Component.svelte
Runes are symbols [...] to control the Svelte compiler. If you think of Svelte as a language, runes are part of the syntax — they are keywords.
Reactive State:
$state()
creates a reactive state variable that automatically updates the UI upon changes.
Deep Reactivity:
Arrays and objects are deeply reactive, triggering updates on property modifications.
Usage in Classes:
Class fields can become reactive state properties with $state()
.
<script>
let counter = $state(0);
const inc = () => counter++;
const dec = () => counter--;
</script>
<button onclick={dec}>-</button>
<output>{counter}</output>
<button onclick={inc}>+</button>
Counter.svelte
Reactive Derivation:
$derived
allows creating reactive variables based on other state variables, updating automatically when dependencies change.
Purity:
Derivations must be pure, with no side effects, ensuring predictable behavior. Mutations are disallowed in derivations.
<script>
let counter = $state(0);
let doubled = $derived(counter * 2);
const inc = () => counter++;
const dec = () => counter--;
</script>
<button onclick={dec}>-</button>
<output>{doubled}</output>
<button onclick={inc}>+</button>
DoubledCounter.svelte
Declaration of Component Props:
$props()
is used to declare component properties, that can be provided from the outside of the component.
Type Support:
Component props can be typed by either using TypeScript or JSDoc comments.
Read-only by Default: Props are read-only unless declared with $bindable()
, preventing unintended mutations and ensuring predictable behavior.
<script lang="ts">
type CounterProps = {
initialValue?: number;
}
let { initialValue = 0 } = $props();
let value = $state(initialValue);
const inc = () => value++;
const dec = () => value--;
</script>
<button onclick={dec}>-</button>
<output>{value}</output>
<button onclick={inc}>+</button>
Counter.svelte
<script>
import Counter from './Counter.svelte'
</script>
<Counter initialValue={10} />
Component.svelte
Logic Blocks in Svelte:
Logic blocks are a core feature in Svelte that allow you to control the flow within your markup.
Familiar Control Structures:
Just like in other programming languages, Svelte offers various control structures through logic blocks. You can use {#if}
and {:else}
for conditional rendering, and {#each}
to create loops.
<script>
let condition = false
</script>
<p> <input type="checkbox" bind:checked={condition} /> </p>
{#if condition}
<p>The condition is true!</p>
{:else}
<p>The condition is false!</p>
{/if}
IfElse.svelte
<script>
let someIterable = [1, 2, 3, 4, 5]
</script>
<ul>
{#each someIterable as item}
<li>{item}</li>
{:else value}
<li>No items found</li>
{/each}
</ul>
Each.svelte
Handling Changes in {#each}
Blocks in Svelte:
When the value of an {#each}
block changes, Svelte adds or removes elements only at the end of the block, while updating existing elements at their current positions in the DOM.
Avoiding Unintended Effects:
To prevent unwanted behavior, especially if elements don’t directly or reactively depend on the change, using unique IDs for each item can help keep things in order.
Snippets, and render tags, are a way to create reusable chunks of markup inside your components.”
Reusable Markup:
Snippets enable the creation of reusable markup within components, reducing code duplication.
Destructuring Parameters:
Snippet parameters can be destructured for convenience and can have default values.
Scope and Visibility:
Snippets can access values from their lexical scope, including sibling components.
<script>
let cards = [{ title: "A Card", content: "This is a card."},
{
title: "Another Card",
content: "This is another card.",
href: "http://somewhere-on-the-inter.net"
}]
</script>
{#snippet cardSnippet(title, content)}
<article class="card">
<header>{title}</header>
<p>{content}</p>
</article>
{/snippet}
{#each cards as card}
{#if card.href}
<a href={card.href}>{@render cardSnippet(card.title, card.content)}</a>
{:else}
{@render cardSnippet(card.title, card.content)}
{/if}
{/each}
Cards.svelte
React to User Input:
Event handlers react allow reacting to user input. They work pretty much the same as in regular DOM-based applications.
Declarative Props:
Event handlers are just component props and can be used as such on components and HTML elements alike.
Events as Callbacks:
Components, that trigger events, do so by accepting and calling a callback property.
<script>
function onsubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
console.log(formData.get("name"));
}
const handleSubmitButtonClick = () => console.log("submit button used")
</script>
<form {onsubmit}>
<input name="name" />
<button onclick={handleSubmitButtonClick}>Submit</button>
</form>
EventHandlers.svelte
Transitioning Elements:
When elements enter or leave the DOM, Svelte enables smooth visual transitions between the states with the transition:
directive.
With the :in
and :out
directives, transition can be run only when an element enters or leaves respectively.
Animate Elements:
When elements within keyed each blocks are re-ordered, they can be animated with the animate:
directive.
<script>
import { slide, fade, fly, scale } from 'svelte/transition';
let showBoxes = $state(true);
let duration = $state(2000);
</script>
<label>Transition duration: <input type="number" bind:value={duration} /></label>
<label><input type="checkbox" bind:checked={showBoxes} /> Show Boxes</label>
<div class="boxes">
{#if showBoxes}
<div class="box cyan" transition:slide={{duration}}></div>
<div class="box magenta" in:fade={{duration}}></div>
<div class="box yellow" out:fly={{y: 200, duration}}></div>
<div class="box key" transition:scale={{duration}}></div>
{/if}
</div>
Transitions.svelte
<script>
import { flip } from "svelte/animate";
let fruits = $state([
"banana",
"apple",
"strawberry"
])
const remove = (fruit) => (fruits = fruits.filter(f => f !== fruit));
</script>
<ul>
{#each fruits as fruit (fruit)}
<li>{fruit} <button onclick={() => remove(fruit)}>remove</button></li>
{/each}
</ul>
Animations.svelte
# https://github.com/nilsroehrig/taskpal
git checkout p1_exercise -b <new-branch-name>
Going Full-Stack
Server by Adrien Coquet from Noun Project (CC BY 3.0)
world by Adrien Coquet from Noun Project (CC BY 3.0)
script by Adrien Coquet from Noun Project (CC BY 3.0)
User by Adrien Coquet from Noun Project (CC BY 3.0)
GitHub
Gitlab
Wrangler CLI
deploy to
Edge Location
Nearby User
world by Adrien Coquet from Noun Project (CC BY 3.0)
Data by Adrien Coquet from Noun Project (CC BY 3.0)
script by Adrien Coquet from Noun Project (CC BY 3.0)
Server by Adrien Coquet from Noun Project (CC BY 3.0)
replicate
replicate
replicate
replicate
replicate
replicate
Edge Location
Regional Data Centre
Object Relational Mapping:
Prisma maps relational databases to type-safe TypeScript or JavaScript objects.
Useful Features:
Prisma comes with a Query Builder and a Migration system, as well as a GUI for managing the database manually.
Compatibility:
Prisma is compatible with most popular relational database system, including PostgreSQL, MySQL and SQLite. It also supports MongoDB and Cloudflare D1.
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
model User {
id String @id @default(cuid())
username String @unique
passwordHash String
}
schema.prisma
export function getUserByName(username: string) {
return prisma.user.findFirst({
where: {
username
}
});
}
export function createUser(userInput: UserInput) {
return prisma.user.create({
data: {
username: userInput.username,
passwordHash: await hash(userInput.password)
}
});
}
user-query.ts
SvelteKit is a framework for rapidly developing robust, performant web applications using Svelte. If you're coming from React, SvelteKit is similar to Next. If you're coming from Vue, SvelteKit is similar to Nuxt.
File-Based Routing:
The path of a route is defined by it's respective location in the file system. The folder src/routes/login
, for example, points to https://your.host/login.
By default, the src/routes
folder points to the root path of the site.
Dynamic Path Parameters:
Path parameters can be used by putting their names in brackets. In consequence, src/routes/posts/[id]
points to a post with the id passed in place of [id]
.
Folder by Adrien Coquet from Noun Project (CC BY 3.0)
page by Adrien Coquet from Noun Project (CC BY 3.0)
The pages markup, styles and behavior. It may receive a data or a form prop.
Loads data on the client and on the server. Returns data available within the data prop.
Loads data and processes forms on the server. Returns data available within data and form.
export function load () {
// any data loading logic
// that is server-only
return {
// any loaded data
}
}
export const actions = {
default() {
// default form action
},
namedAction() {
// named form action
}
}
+page.server.js
export function load () {
// any data loading logic
// that can run on client
// and server
return {
// any loaded data
}
}
+page.js
<script>
let {
// data loaded in
// load functions
data,
// data returned from
// form actions
form
} = $props();
</script>
<h1>Route Headline</h1>
+page.svelte
API routes:
In addition to regular pages that render HTML, SvelteKit also allows for API routes defined in +server.js
files.
HTTP Method Functions:
The file path still dictates the URL path, but +server.js
files export functions named after HTTP methods: e.g. GET, POST, PUT, DELETE. They take RequestEvents and return standard Response objects.
export function GET() {
const randomQuote = getRandomQuote();
return new Response(randomQuote);
}
export async function POST({request}) {
const { quote } = await request.json();
const addedQuote = addRandomQuoteToPool(quote);
return Response(JSON.stringify(addedQuote));
}
/api/random-quote/+server.js
Load Functions:
In SvelteKit, data is loaded via load functions. These are responsible to gather all data a page needs, in order to be rendered properly.
Universal Load Functions:
Defined in +page.js
, universal load functions can run on the server and the client. They should not handle any sensitive or private data.
Server Load Functions:
In contrast, server load functions only run on the server, making them ideal for e.g. database access or usage of sensitive information. They are defined in +page.server.js
.
src/resource/[id]/+page.server.js
export function load ({ params, locals }) {
const resource = db.getResourceById(params.id);
if(!resource) {
error(404);
}
if(!canAccessResource(resource, locals.user)) {
error(403);
}
return {
resource
}
}
HTML Form Processing:
Form actions in SvelteKit serve to process standard HTML forms. They can work without client-side JavaScript but can be progressively enhanced.
Default vs. Named Actions:
Form actions are defined in +page.server.js
files alongside load functions. It is possible to either have one single action called default, or an arbitrary number of named actions, not both at the same time.
The form Property:
On a page with a form action, an optional form property is available. It is populated by returning data from an action.
<script>
import { enhance } from '$app/forms';
const { form } = $props();
</script>
<form method="POST" use:enhance>
{#if !form.success}
An error occurred!
{/if}
<input type="email" name="email">
<input type="password" name="password">
<button>Submit</button>
</form>
+page.svelte
export const actions = {
default({ request }) {
const formData = await request.formData();
const success = validateCredentials(
formData.get("email"),
formData.get("password")
)
if(!success) {
return fail(400, { success: false });
}
return { success: true };
}
}
+page.server.js
Runtime Validation:
To validate data at runtime, Zod can be used. It is a 3rd-party library an not part of SvelteKit.
Zod Schemas:
At the heart of Zod are schemas. These are type parsers defined programmatically by the application. They allow to parse and validate arbitrary data, using the Schema.parse()
method.
Type Safety:
When using TypeScript, proper types can be easily inferred from Zod Schemas.
import { z } from 'zod';
export const UserSchema = z.object({
name: z.string(),
age: z.number().optional(),
dateOfBirth: z.coerce.date()
});
export type User = z.infer<typeof UserSchema>;
// passes
let user = UserSchema.parse({name: "Karl", dateOfBirth: new Date()});
// throws
let user = UserSchema.parse({name: 3537, dateOfBirth: new Date() });
// throws
let user = UserSchema.parse({name: "Marlene", dateOfBirth: "not-a-date" });
User.js
Easy FormData Validation:
When using Zod with FormData objects, some transformation is needed. The library zod-form-data helps with this.
Good Pairing with Form Actions:
Since SvelteKit form actions are using FormData, they pair exceptionally well with zod-form-data.
Parse FormData with Zod Schemas:
zod-form-data allows using either built-in validators or passing a Zod Schema which is then applied to FormData.
import { zfd } from 'zod-form-data';
const Credentials = zfd.formData({
email: zfd.text(),
password: zfd.text()
});
export const actions = {
async login({request}) {
const credentials = Credentials.parse(
await request.formData()
);
const user = await loginWithCredentials(credentials);
return {
user
}
}
}
+page.server.ts
# https://github.com/nilsroehrig/taskpal
git checkout p2_exercise -b <new-branch-name>
Authenticate & Authorize
alt
Client
Server
Database
sends credentials
validates
credentials
[credentials valid]
create
session
stores session
returns session cookie
[credentials invalid]
returns auth error
requests data with session cookie
queries session data
return session data
validates
session
alt
[session is present]
returns requested data
returns auth error
[session is missing]
Hooks in SvelteKit are powerful functions that let you control different behaviors across your entire app.
Types of Hooks:
SvelteKit provides three types of hooks: server hooks, shared hooks, and universal hooks.
Server Hooks:
These run exclusively on the server. For example, the handle()
hook runs whenever a request hits the server, making it perfect for tasks like handling authentication or integrating middlewares.
Shared Hooks:
Shared hooks, like handleError()
, can be defined for both the server and the client, although in separate files. This makes them ideal for handling unexpected errors in a centralized way, regardless of where they occur.
Universal Hooks:
These hooks function seamlessly across both server and client environments, giving you flexibility in how you manage application behavior.
export function handleError({error}) {
// do error handling stuff
return {
message: "Error message",
//... other data
}
}
hooks.client.js
import { redirect } from '@sveltejs/kit';
export function handle({event, resolve}) {
if(event.url.pathname.startsWith("/api")
&& !userIsAuthenticated()) {
redirect(302, "/login")
}
return resolve(event)
}
hooks.server.js
File-Based Layouts in SvelteKit:
Just like routes, layouts in SvelteKit are organized based on the file structure.
Default Behavior:
By default, a layout applies to the route in the same folder and all its nested routes.
For instance,
src/routes/+layout.svelte
automatically applies to:
src/routes/+page.svelte
src/routes/login/+page.svelte
, and so on.Inheritance of Layouts:
Layouts can also inherit from other layouts higher up in the folder structure.
For example, src/routes/+layout.svelte
wraps around src/routes/login/+layout.svelte
, making it easy to manage nested layouts.
Layout Groups:
When some specific nesting of layouts is required but a change of routs is undesired, pages can be grouped by folders in parantheses: For example
src/routes/(authenticated)/
could be used to group authenticated pages without altering the route hierarchy.
Your
+layout.svelte
files can also load data, via+layout.js
or+layout.server.js
.
<script>
let { children } = $props();
</script>
<div class="wrapper">
<header>Headline</header>
<main>
{@render children?.()}
</main>
<footer>Footer</footer>
</div>
+layout.svelte
# https://github.com/nilsroehrig/taskpal
git checkout p3_exercise -b <new-branch-name>