We used SvelteKit to make https://typedwebhook.tools - which allows you to test HTTP requests and automatically generates typescript types for the body. Inspired by that, let's run through how to make a realtime websocket UI using SvelteKit.
If you want a sneak preview, the code for typed webhooks is open source: https://github.com/inngest/typedwebhook.tools
Why SvelteKit?
- Using Vite, it builds fast in dev. And it has HMR with state persistence out of the box. Somehow this is consistently broken in every react app, no matter what kit you use :D
- It has SSR out of the box. It’s built for progressive enhancement, and configuring pre-rendering is the easiest I’ve seen
- State management is easy. It’s easy to work with stores. You can (broadly speaking) use the stores from anywhere: no top-level context necessary (ahem, hello websockets!)
- SvelteKit comes with a standard way of doing things (CSS, JS, forms, state, routing), so it’s easy to work with and it’s easy to share amongst devs. It’s easy to get set up and running with your entire framework — think a mixture of NextJS and create-react-app for Svelte.
Plus, it's amazingly developer friendly
Getting started
Make sure that you have Node & NPM installed, then run:
npm init svelte@next
It'll run you through a guide to set up your base project. Here's how we answered those questions:
✔ Where should we create your project?
(leave blank to use current directory) … realtime-demo
✔ Which Svelte app template? › Skeleton project
✔ Use TypeScript? … Yes
✔ Add ESLint for code linting? … Yes
✔ Add Prettier for code formatting? … Yes
Let's go to that directory and run the dev server:
cd ./realtime-demo && yarn dev
If you go to localhost:3000 you should see Svelte up and running!
The current code lives in ./src
. The routes folder (./src/routes
) acts as a router: ./src/routes/index.svelte
is the index page rendered by default, and ./src/routes/about.svelte
is rendered when you navigate to /about.
You might be asking yourself "where do shared components go?". They go in ./src/lib
which isn't made by default.
Let's jump to real-time state - the meat of what we're building.
Real-time state
State is saved in stores
. A store is similar to react's useState
value, but way more powerful. We're going to be creating a store that records websocket responses.
Let's make a file for our store in the shared directory: ./src/lib/state.ts
.
Inside it, we're going to use Svelte's writeable stores:
// Import the function which initializes a new mutable store.
import { writable } from 'svelte/store';
type Item = {
id: string;
content: string;
};
// Our store will record an object containing an array of
// items produced by the websocket.
type State = {
items: Array<Item>;
error?: string;
};
// That's it; state is now usable! Components can subscribe
// to state changes, and we can mutate the store easily.
//
// Note that this is a singleton.
export const state = writable<State>({
items: []
});
// We also want to connect to websockets. Svelte does
// server-side rendering _really well_ out of the box, so
// we will export a function that can be called by our root
// component after mounting to connnect
export const connect = (socketURL: string) => {
const ws = new WebSocket(`wss://${socketURL}`);
if (!ws) {
// Store an error in our state. The function will be
// called with the current state; this only adds the
// error.
state.update((s: State) => return {...s, error: "Unable to connect" });
return;
}
ws.addEventListener('open', () => {
// TODO: Set up ping/pong, etc.
});
ws.addEventListener('message', (message: any) => {
const data: Item = JSON.parse(message.data);
// Mutate state by prepending the new data to the array.
state.update((state) => ({ ...state, items: [data].concat(state.items) }));
});
ws.addEventListener('close', (_message: any) => {
// TODO: Handle close
});
}
We can now use this in our index page, ./src/routes/index.svelte
:
<script context="module" lang="ts">
export const prerender = true;
</script>
<script lang="ts">
import { onMount } from 'svelte';
// $lib auto-resolves to ./src/lib in Svelte.
import { state, connect } from '$lib/state';
onMount(async () => {
connect();
});
</script>
<!--
We haven't defined the ItemList component (which should
go in ./src/lib too), but this shows how you can auto-
subscribe to the store using `$state`. Every time state
updates, $state.items changes and this will re-render
-->
<ItemList items={$state.items} />
This shows the power of Svelte, SvelteKit's routing, and state management. You can access state from anywhere in your app - no component hierarchy needed - and it's super easy to use within your components.
Svelte is incredibly powerful and developer efficient. Give it at try!
Top comments (1)
That's a cool sneak peak.. I wish you could go on and get into details.