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(usuallyapp/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") %>