DEV Community

Christian Sedlmair
Christian Sedlmair

Posted on • Edited on

Setup Svelte as Custom Element on Vite

Overview

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: '-' })
Enter fullscreen mode Exit fullscreen mode

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!"/>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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({})
    ]
  })
Enter fullscreen mode Exit fullscreen mode

package.json

{
  ...
  "type": "module" // => otherwise @sveltejs/vite-plugin-svelte would break
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Overview

Top comments (1)

Collapse
 
patricknelson profile image
Patrick Nelson • Edited

Also check out svelte-retag which is a fork of Chris' svelte-tag and supports nesting as well as slots.