Svelte on rails

Seamless and robust Svelte in Rails integration. Can render server-side, always or only on first request, and handle subsequent turbo requests as custom elements without shadow-dom. Server-side compilation via rollup.

Server-side rendering is the bottleneck in this pipeline. Although rollup is mighty, this gem is in a early state and there sure are 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 did not test it, so require for example 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 chrisward, shakacode and ElMassimo because they all helped me with theyr gems.

Requirements

Installation

add

gem "svelte-on-rails"

to your gemfile

and run

$ rails svelte_on_rails:install

this creates a config file, please check the comments there.

setup the npm module @csedl/svelte-on-rails on the client side

Usage

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

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'
  • <script lang="ts"> (svelte component with typescript syntax)
  • import { someFunction } from '../customJavascript.js';
  • import Child from './Child.svelte';

Option render_server_side: :auto

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 not present), then does nothing server-side but add a tag. Assuming the filename is HelloWorld.svelte:

= svelte_component("HelloWorld", message: "Property from the server", render_server_side: :auto, class: "box")
#=> output on the rails-console:
Added Svelte-Tag for custom element: «<hello-world-svelte class='box svelte-component'></hello-world>»

If configured on the npm module, it instaniates a custom element and adopts it to the element.

This works perfectly with hotwired/turbo because the custom elements are built on the first page load and survive page visits from turbo. If you then visit a page that has the %hello-world tag, the component will appear immediately. This is not a default setting, as it requires more configuration.

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.

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.