My experience coming to SvelteKit from Rails and Ruby
This is specifically because my experience in webdev is very informed by Ruby on Rails. I've done plenty of work with strongly typed languages, but never webdev. My experience is also from 2018 and older, so there may well be modern solutions that achieve parity with the clever things I'm finding in SvelteKit that I feel were not as present in Rails work. Don't at be bro!
Strong types are extraordinary at catching inter module communication issues. Any time you’re putting polymorphic messages into Redis, etc. Also it's very nice having a strongly typed, schema-aware database client. The auto-completion is very useful.
Idea: make some playgrounds to show examples live
Sometimes the VSCode plugins don’t update when you push prisma schema changes So many VSCode plugins! So many code warnings. Most things seem to have a Typescript option though. Sometimes you have to faff around with type definitions, and that's pain. Interpreting objects using ‘as’ doesn’t necessarily do what I thought! There is no enforcement in Typescript land, as it disappears at runtime!
It’s not a kitchen sink framework like Rails or Django, so you need to do a lot of discovery for the best ecosystem extensions to use. UI frameworks, form frameworks, validation, auth, database ORM, background jobs. Gah!
Swift with Codables is better than Svelte + Zod, as it's just part of the language. More hoops to jump through to perform adequate validation. There is however a LOT of discussion online so it's easy to find discussion around things you need to solve.
Async/Await is just cool. Very elegant.
Svelte is great. Where Rails is good at the core, but gets sketchy with hotwire, it’s the inverse for Svelte. You stay in one language front and back. There's no swapping of languages, only swapping of execution context. I think it's very slick.
Swift observables and data binding is clever, but I've seen it misused for things like tracking server connections to URLs. Maybe not misused, but I wouldn't use it for everything, and people reach for it more than I would. Tasks like tracking the number of people connecting to a specific URL. It just won't scale unless you're only ever on one single server host. There's perhaps a lack of enterprise or scale thinking with much of the svelte examples. They are small-time.
Not at all clear how to set up VSCode with the terminal running the dev server, dev syntax checker, etc. I need to make a specific section about this. Get THESE extensions, add THESE npm run ...
commands to package.json
, configure VSCode to show you THIS view.
No pluralisation functionality in the template language like Rails ActionView. I'd LOVE a nice way to display 'Deleted 3 rows', instead of the crass 'Deleted 3 row(s)', or worse, entity/entities.
Promise programming gets complex where you need to handle promise rejections. Needs a section.
Patterns like establishing type safety that works both at compile time and runtime is not obvious. Be aware of things like interface
being TS only, and by the time your code runs, the interface has vanished, it's NOT runtime.
Understand that export let data;
is the way that incoming data comes into the frontend. This is the equivalent of any template @variables
you provide. Only here, they are kept as variables, so you're doing more of a binding operation. You also have events on the client side. onMount
and onDestroy
. Need to explain page lifecycle.
I wish I had paid more attention here, as the syntax in Svelte 4 is very terse, and very very magic. I should map it to Swift UI ObservableObject
/ StateObject
macros. The $
dereference operator isn't like old jQuery or whatever. I initially thought it was some kind of scoping operator! I should use my form snap slider example to show it off. Svelte 5 Runes makes this more explicit, so it will be better for newcomers.
The vitest
setup out the box was extremely minimal, and I wish it did more. I had to:
- Write my own db helpers to push, clear and remove the db via
vitest
hooks - Configure
setupFiles: ["./src/tests/setup.ts"]
to point to a file where I can definebeforeAll(), beforeEach(), afterAll()
and clean up the db. - Also use
globalSetup
to define abeforeAll()
that really does run once per test invocation, becausesetupFiles
runs before and after each test file. I was initially mistaken in thinking I only neededsetupFiles
. - I create and nuke (rm) the DB after each test to keep things tidy
- I had to disable parallelism with
fileParallelism: false
in thevite.config.ts
file to prevent DB trampling, because it (not unreasonably) wants to run things in parallel.
Coming from Rails I really wanted the equivalent of RAILS_ENV=test, which was the highest level control of what mode you were running in.
With dotenv you multiple levels in the hierarchy. You can have .env, .env.local, .env.production. But you also get to override them with pre-existing environment variables done at the process level (naturally).
Vite ALSO has its own way of doing dotenv interpretation. https://vitejs.dev/guide/env-and-mode
Vite recommends:
To prevent accidentally leaking env variables to the client, only variables prefixed with VITE_
are exposed to your Vite-processed code. e.g. for the following env variables:
This is also to help with intellisense.
Svelte THEN layers on https://kit.svelte.dev/docs/configuration#env
Which says $env/static/public
vars should start with PUBLIC_
and privatePrefix
defaults to ..... empty string!! HAHAHA
For someone who is used to running their own long running processes in a customised iTerm2, the idea of invoking the dev server inside VSCode felt weird, but it's useful when error messages come up from run dev
or check:watch
as it turns a file and line number into a hyperlink which takes you to that place in the document immediately, no change of focus.
For any long running process like subscription functionality, you must remember to use return to pass back a rejection function. That way, when you navigate away from the page, anything done in onMount
can be stopped gracefully.
onMount(() => {
// Must return the result of this subscribe as a reject function
return flash.subscribe(($flash) => {
...
}
})
You will get cryptic messages like:
Unhandled Promise Rejection: TypeError: Importing a module script failed.
Don't accidentally do this:
export let data: PageData;
let ct = data.myCoolThing;
As a way to prevent you from needing to type data.myCoolThing.someProp
in the template over and over, and you can just do ct.someProp
. It does work to begin with, but it's not correct.
This will decouple your ct
var from being an observable state changing object like data
is. You will start noticing funny things like programatic use of goto()
to move people around your site will result in pages not rendering with up-to-date state. Even though you're on /page/2
you'll still be seeing data for /page/1
unless you do a full browser reload. Svelte wants to keep full page refreshes to a minimum.
Instead, do this:
export let data: PageData;
$: ct = data.myCoolThing; // or const ct = $derived(data.myCoolThing) in Svelte5