Svelte Components within app/views

What do we write in app/views .html.erb files? HTML + rendering logic. And we are deeply intertwined with ActiveRecord data. Svelte components fit exactly in this.

So, why not place them in the same folder? We found: let the developer decide. Developing of this feature felt clean, not hacky.

By default, this all is disabled.

Introduced with v10 this feature is experimental.

Pros/Cons: react_on_rails does this deliberately not, because of the separation of techniques. I thought: With Rails-UJS, we already had JavaScript mixed within app/views. And Svelte’s slimmer compiling and rendering process fits this idea. So why not let the developer decide?

By default, all of this is disabled.

Now, we want to fetch Svelte components outside of the sourceCodeDir (usually app/frontend).

How is this possible in JavaScript while staying fully configurable and maintaining security? By Vite’s ingeniously import.meta.glob!

On the build step, it collects the mapped components in the assets. On initial load, with eager: false, it loads only the mapped structure. This keeps the initial load fast and maintains security as a client would have no option to dig deeper into the filesystem.

Configuration

Example configuration to keep app/frontend/components as the default location for Svelte components but enable app/views.

You may have a look at the vite-ruby aliases and Entrypoints in theyr docs.

svelte-on-rails

Enable the view helpers to see components within app/views.

config/svelte_on_rails.rb

vite_source_dir: app/frontend #=> unchanged
components_subdir: javascript #=> unchanged
extra_component_paths:
  - app/views #=> uncomment this

default_components_root: components_subdir

aliases:
  #'@frontend': app/frontend/javascript
  '@views': app/views  #=> uncomment this

Vite

vite-ssr.config.ts

function getSvelteEntries(): string[] {
    const patterns = [
        'app/frontend/**/*.svelte',
        'app/views/**/*.svelte' // < add this
    ]
    const files = fg.sync(patterns, {absolute: true})
    const entries = files.map(file => path.relative(process.cwd(), file))

    console.log(`[svelte-ssr] Found ${entries.length} Svelte components for server side compilation.`)
    return entries
}

After running npm run build:ssr you should see the compiled components within public/svelte-ssr/.vite/manifest.json, example-entry:

{
  "app/views/articles/onView.svelte": {  //<=importand
    "file": "assets/onView-4vs8vDmo.js",  //<=importand
    "name": "onView",
    "src": "app/views/articles/onView.svelte",
    "isEntry": true, //<=importand
    "imports": [
      "_escaping-CEQL8yRK.js"
    ]
  }
}

Frontend

On app/frontend/entrypoints/application.js:

import {SvelteOnRails} from '@csedl/svelte-on-rails';
SvelteOnRails.debug = true; 
// if you want to see what it does
SvelteOnRails.lazyComponents = import.meta.glob([
        "/../../app/views/**/*.svelte",
        "/**/*.svelte"
    ],
    {eager: false, import: "default"}
)

When you render a example component within app/views, hydration should work! 🌟 🏆

Example

<%= svelte("@views/article/helloArticle") %>

would render app/views/article/helloArticle.svelte now and hydration should work — the component’s JavaScript is alive! 😻🥂

For the Javascript part you can use the aliases and import svelte components or anything from the frontend-folder into app/views and vice versa.

Extra: app/views as default location for components

vite_source_dir: app/frontend
components_subdir: javascript
extra_component_paths:
  - app/views

default_components_root: app/views # => uncomment this

aliases:
  '@frontend': app/frontend/javascript #=> uncomment this
  '@views': app/views  #=> not necessary

For example, within app/views/articles/edit.html.erb

<%= svelte("helloArticle") %>

would render the component app/views/articles/helloArticle.svelte.

This is the same as:

<%= svelte_component("@views/articles/helloArticle") %>

Copyright © 2025-2027 sedlmair.ch. Distributed by MIT license.