Why Svelte?
In every app, we have at least one feature that is special and requires complex front-end logic. This is where Svelte gives us and our customers so much pleasure.
Together with Capybara, which is the default in Rails, test-driven Javascript development is easy and intuitive, see th last paragraph on the bottom of this page.
Compared to React, which clearly has a larger ecosystem, Svelte is leaner, faster, better integrated, and in my opinion, simply the better technology. To understand the idea of Svelte in comparison to React, please take a look at this entertaining video from Rich Harris, the founder of Svelte, starting at minute 4:13 rethinking reactivity.
Why Svelte as Custom element?
Why work with custom elements / web components? Rich Harris doesn't like them. The reason is Hotwired/Turbo and component initialisation. You can only initialise the svelte component in a regular element if the element is present in the current view! If you would push a svelte element by turbo-stream then you would need an extra initialisation step. building it as a web component can be done on first page load while none of those elements are present and then you can push it to the front at a later time.
Svelte itself has an option to build custom elements, but they build a shadow DOM, which means global styles are not applied! There's an open issue from 2018 that will be fixed in Svelte 4. In the meantime, we use chriswards svelte-tag which solves this.
NOTE: server-side-component-api also solves this.
Example Code
application.js
import { initSvelte } from "vite-svelte-initializer";
const apps = import.meta.glob('../javascript/components/**/*.svelte', { eager: true })
initSvelte(apps, 2, { debug: true, exclude: ['components'], folderSeparator: '-' })
vite-svelte-initializer
uses use svelte-tag like mentioned above.
Options for naming see on vite-svelte-initializer
inside any view
<hello-svelte title="Hello Svelte!"/>
frontend/components/hello-svelte.svelte
<script>
import getCSRFToken from '@shopify/csrf-token-fetcher';
import axios from "axios";
export let title
let input_value
let items = ['one', 'two', 'three']
let handle_input = () => {
console.log(`input value has changed to "${input_value}"`)
}
function handlePost () {
axios.post('/svelte/push2', {authenticity_token: getCSRFToken()}).then(res => {
console.log(res.data);
})
}
</script>
<h1>title: {title}</h1>
<input on:keyup={handle_input} bind:value={input_value}>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
Installation
together with svelte we setup axios and the @shopify/csrf-token-fetcher for enabling post requests by axios.
$ npm i svelte @sveltejs/vite-plugin-svelte vite-svelte-initializer axios @shopify/csrf-token-fetcher
vite.config.js
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
+ resolve: {
+ dedupe: ['axios']
+ },
plugins: [
RubyPlugin(),
+ svelte({})
]
})
package.json
{
...
"type": "module" // => otherwise @sveltejs/vite-plugin-svelte would break
}
Restart server.
Example Code, from above, should work.
Test with Capybara
The best method from Capbybara is the find
method, because it finds only visible elements. This is an example using rspec:
it 'create one article' do
# Test the UI
visit new_customer_article_path(customer)
within 'form#new_article' do
find('input#article_number').fill_in(with: 'my-number')
find('input#article_title').send_keys('my-title')
find('input[type="submit"]').click
end
expect(page).to have_css('.callout.success')
# Check if the record is stored in the database
expect(Article.last.number).to eq('my-number')
end
Top comments (1)
Also check out svelte-retag which is a fork of Chris'
svelte-tag
and supports nesting as well as slots.