Svelte on rails

Seamless and robust Svelte in Rails integration.

By default, and when installed together with hotwired/turbo, it renders
svelte components on first request server side («SSR») and for subsequent
requests it provides a empty tag that will be mounted on the frontend by the associated npm package @csedl/svelte-on-rails on the frontend.

This way svelte works perfectly together with turbo. You will never notice
this unpleasant «blink» on the frontend and it is maximum performance
optimized. SSR compilation is handled by rollup.

Server-side rendering is the bottleneck on such a pipeline.
Although rollup is powerful, this gem is in an early state
and there may be limitations.

Javascript is written in ESM syntax, orientated by the functionality of vite.
Although the common-js plugin is installed, I have not tested it,
so for example require may not work in Svelte components.

But we have done everything we can to make your setup work smoothly.

This all is developed on Rails-7 together with vite_rails.

Thanks to shakacode
and ElMassimo because they all helped me with theyr gems.

If you have issues, please open one and contributors are welcome!

Requirements

Setup from cero

If you want to start from a new rails app, follow theese tutorials

Installation

within the app folder

  bundle add svelte-on-rails
  rails svelte_on_rails:install_for_vite_and_turbo

This will create a little config file, istalls the npm package,
creates a initializer file and adds the import statement on appplication.js entrypoint file
and it adds a HelloWorld.svelte component.
If not installed, it installs @hotwired/turbo-rails too.

Add a controller and a view with the view helper and you should see «Greetings from Svelte on your view».

There is also a smaller installer:

$ rails svelte_on_rails:install

that does nothing else than creating a config file.

Within the config file, there are two tags:

  • frontend_folder (relative to Rails.root)
  • components_folder (relative to frontend_folder)

Set up the npm module @csedl/svelte-on-rails
on the client side.

Test if it works

On the view

= svelte_component("myComponent", items: ['item1', 'item2'])

Above mentioned installer adds HelloWorld.svelte, like so:

  export let items
  let count = 0;

  function increment() {
    count += 1;
  }


<h1>Greetings from svelte</h1>

<ul>
  Increment: {count}
  {#each items as item}
    <li>{item}</li>
  {/each}
</ul>


  button {
    background-color: darkred;
    color: white;
    padding: 10px;
    border: none;
  }

And you should see «Greetings from svelte» on the browser.

Click the counter button and check if the component is alive.

Without the npm package installed,
or by passing render_server_side: true and hydrate: false to the view helper,
you would see the same html, and the styled button,
but the increment button would not work.

I inserted very detailed error messages on rails and on the
npm package, for making sure that thisk setup is working.
So please check the logs on your browser.

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 SSR request
  • Rendered MyComponent.svelte as empty element that will be mounted on the client side
    • => subsequent calls
  • Completed 200 OK in 521ms (Views: 520.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.3ms)

Testing

Testings are within the gem by rspec. And there is a rails project with
testings (based on playwright) where the npm package and this gem is tested together.

Licence

License is MIT