Setup Svelte as Custom Element on Vite
Tags: svelte, rails, vite
Published: 2022-09-20 13:51:52

[Overview](https://dev.to/chmich/setup-vite-svelte-inertia-stimulus-bootstrap-foundation-on-rails-7-overview-1bk1)

# 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](https://svelte.dev/blog/svelte-3-rethinking-reactivity).

# Why Svelte as Custom element?

Why work with custom elements / web components? [Rich Harris doesn't like them](https://dev.to/richharris/why-i-don-t-use-web-components-2cia). 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](https://github.com/sveltejs/svelte/issues/1748) that will be fixed in Svelte 4. In the meantime, we use [chriswards svelte-tag](https://www.npmjs.com/package/svelte-tag) which solves this.

NOTE: [server-side-component-api](https://svelte.dev/docs/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](https://gitlab.com/sedl/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](https://axios-http.com/) 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
```

[Overview](https://dev.to/chmich/setup-vite-svelte-inertia-stimulus-bootstrap-foundation-on-rails-7-overview-1bk1)