Svelte on Rails
Seamless and robust Svelte in Rails integration.
By default, and when installed together with @hotwired/turbo-rails
, 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
- actual node installed on the server
- tested on ruby 3.2.2 and rails 7.1
- svelte v5 (see: how to install svelte on rails/vite)
- turbo (recommended / how to install turbo on rails)
- if you use special packages (like pug) that requires commonjs, you may need
Installation from cero
together with haml, vite, svelte and turbo
If you want to start from a new rails app, follow theese tutorials
rails new my-test-app --skip-javascript
within the app add the gem
bundle add svelte-on-rails
and run the installer
rails g svelte_on_rails:install --full
The --full
contains:
--vite
- adds vite_rails gem and running the installer
--haml
- adds the gem and converts existing views
svelte_on_rails
- This is not a option, this is always done: Adds a config file and installs
@csedl/svelte-on-rails
by npm
- This is not a option, this is always done: Adds a config file and installs
--turbo
- installs
@hotwired/turbo-rails
and adds import statement to application.js
- installs
--svelte
- adds or updates
svelte
- adds or updates
--hello-world
- adds a hello world component
You can also use the options you want instead of using --full
The installer is written carefully: It does not overwrite a file without asking.
It tells you all what he is doing.
At the end, you just have to (re-) start your server
and you will see a Svelte component.
Then, you can run
rails svelte_on_rails:remove_hello_world
and start coding.
Minimal Installation
within the app folder
bundle add svelte-on-rails
Run the installer
rails g svelte_on_rails:install
that does:
- creating a config file.
- installs the npm package
please follow the instructions on @csedl/svelte-on-rails
for setup the workflow
Within the config file, there are mainly two important tags:
frontend_folder
(relative to Rails.root)components_folder
(relative to frontend_folder)
Check if it all works
Add a controller and a view with the helper = svelte_component('HelloWorld', items: ['item1', 'item2'])
.
On loading you will see
- Rendered HelloWorld.svelte server-side: 0.075ms
On the rails console and you will see «Greetings from svelte»
on your view with a styled «Increment» button.
Styles you find within the svelte component.
Click this button and see that the component is alive.
On @csedl/svelte-on-rails
are details how the frontend part is working.
Without the npm package installed,
or by passing ssr: 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';
Precompile assets
When the rails app runs assets:precompile
a additional rake task is triggered: rails svelte_on_rails:reset_and_compile_all
.
This is not absolutely necessary but it reduces the loading time of the very first calling of the first component.
So, on deploying or doing rails assets:precompile
you shoud see something like:
-------------------------------------------------------------------------------- compiled 1/3: javascript/components/Pug.svelte compiled 2/3: javascript/components/SvelteOnRailsHelloWorld.svelte compiled 3/3: javascript/components/sub/NestedComponent.svelte Svelte on Rails: Reset dist and compile-all executed --------------------------------------------------------------------------------
on the console.
Option ssr: :auto
ssr: :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 ssr: :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.
More rake tasks
This tasks are more for testing/playground purposes
rails svelte_on_rails:add_hello_world
rails svelte_on_rails:remove_hello_world
rails svelte_on_rails:toggle_hello_world_svg
toggles the svg nested to the hello world component for check if thee svg is refreshed when imported image changes
rails svelte_on_rails:reset_and_compile_all
This does the same step that ist triggered together with the
rails assets:precompile
step together with the deployment pipeline:
it removes all contents of the svelte-on-rails compiled
assets and compiles them all new.
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
. Mainly there are 2 types of tests:
compiler tests
The folder spec/rails_vite_test_app
holds a package.json
with the @csedl/svelte-on-rails
package, mainly because of the
peer dependencies necessary for ssr tests, and some example components.
This is mainly for testing the compiler.
Fully complete tests
The folder spec/installer_tests
, mainly, is emptied
before test. Tests there are starting by rails new
followed
by running the installer.
NOTE: Theese tests are dependend on your environment, including the running ruby version!
I am woring on rvm
If you work on a different environment, tests may have to be adopted.
The current test cases including (among others):
- create a completely new rails app, running the full installer and check if a
hello World
component is visible and javascript is working. - run
assets:precompile
within a rails app and check if the gem does its precompiling too.
Development helpers:
rake svelte_on_rails:create_test_app
This creates a new rails app within spec/installer_tests
and runs the installer with --full
.
On the last line it shows you the path to the test app.
Now you can cd into this folder and run rails s
.
rake svelte_on_rails:replace_test_app_hello_world
Overwrites the hello world files within this test app from template.
As a rule of thumb, for component tests, before each test, templates are copied onto the tests-app, and be tested by playwright and the rails server should remain running at the end of each component test.
On that way the templates easily can be developped and tested, the end user has maximum function stability and the developer always see the latest changes.
Idea is to have all working examples within the template and included in the testing scope as in the hello world component.
… for having a 100% WYSIWYG and a easy maintainable package so that contributors are motivated to come into my project.
Licence
License is MIT