Svelte on rails
Seamless and robust Svelte in Rails integration.
Can render server-side (SSR), always or only on first request, and handle
subsequent turbo requests by mounting svelte components
on an empty element for server-side performance optimisation.
Server-side compilation via rollup.
Server-side rendering is the bottleneck on such a pipeline.
Although rollup is powerful, this gem is in an early state
and there are certainly limitations.
So please always test your components early if they pass ssr.
I have written everything in ESM syntax.
Although the common-js plugin is installed, I have not tested it,
so for example require
may not work in Svelte components.
To check which import statements work, please check the components
in the spec/rails-vite-test-app/app/frontend/javascript/components
gem.
This all is developed on Rails-7 together with vite_rails
.
Thanks to shakacode
and ElMassimo because they all helped me with theyr gems.
Requirements
- actual node installed on the server
- tested on ruby 3.2.2 and rails 7.1
- svelte (see: install svelte on vite/rails)
Installation
add
gem "svelte-on-rails"
to your gemfile
and run
$ rails svelte_on_rails:install
This will create a little config file, please read the comments there.
Set up the npm module @csedl/svelte-on-rails on the client side.
Usage
= svelte_component("myComponent", items: ['item1', 'item2'])
would render a component like myComponent.svelte
export let items <ul> {#each items as item} <li>{item}</li> {/each} </ul> ul { list-style: none; }
Import statements
For check which statements are working and actively tested, please check the
components folder within the gem specs.
Among others there are
import svg from '../example.svg?raw'
- “ (svelte component with typescript syntax)
import { someFunction } from '../customJavascript.js';
import Child from './Child.svelte';
Option render_server_side: :auto
render_server_side: :auto
is the default option, as set on config file and can be overridden on the view helper.
Works with hotwired/turbo
only
By passing the render_server_side: :auto
option to the view helper,
it checks if the request is an initial request (request header X-Turbo-Request-ID
is present):
On Initial-Request, it adds the attribute data-svelte-on-rails-initialize-action="hydrate"
and
returns a server side rendered component that will be hydrated on the frontend by the npm package.
Otherwise it adds mount
instead of hydrate, and renders a empty element, but provided with the
necessary attributes for the npm package.
More details to this point you can find on the npm package.
This works perfectly with hotwired/turbo because the javascript is only
loaded on very first load to the frontend, then the most work is done
in frontend and the server is relieved, except on initial request.
You will see no unpleasant «blink» on the page.
Styles
For 99% of use cases you can just skip this chapter.
You can simply work with global styles as well as styles within the svelte component.
A server-side rendered svelte component has 2 states:
Before hydration
- The
svelte_component
view helper renders the styles contained within the component into a style tag within the component’s wrapper element. This has to be done this way because of Turbo. - In very, very rare cases, global styles are not applied in the same way as after hydration.
After hydration
- Svelte adds a style tag inside the header
- Svelte renders the component again, which removes the style tag inside the component wrapper.
If you notice a "blink”
For the app to look stable, both states must appear in the same way.
Normally this is the case. But if there are problems,
or you want to see the state before hydration, for development purposes, you can pass
the hydrate: false
option to the view helper,
and no hydration will happen for this component.
Performance
Example from the rails console for a medium complex component
- Compiled MyComponent.svelte.js: 0.411ms
- => happens only once
- Rendered MyComponent.svelte server-side: 0.518ms
- => happens on every request
- Completed 200 OK in 521ms (Views: 520.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.3ms)
Best is to work with the render_server_side: :auto
option, which you can
set as default on the config file. Then, the server does the SSR only on
initial request and the rest happens on the frontend.
Testing
Testings are within the gem by rspec
. And there is a rails project with
testings (based on playwright) where some examples are built in.