TUTORIAL Using Svelte with optional TypeScript support in Rails 7 with Vite

In this tutorial we will take a look at how to integrate Svelte with optional TypeScript support in a Rails 7 project, with help of the Vite JS bundler.

This tutorial is for a fresh created Rails 7 project, or an existing Rails app with vite-rails already installed.

If you need to use Svelte in an existing Rails 7 default esbuild project, read this Reddit post on how to use Svelte components through a Stimulus controller.

Create a new Rails 7 project

Create a new project without default javascript options

First, we will create a new rails app without the default javascript bundler option.
We will use Vite as our bundler. I will call this app vrs-app (Vite Rails Svelte App).

rails new vrs-app --skip-javascript

Or you can create the app with other options.
It is better to also skip asset pipeline, as we are very unlikely to use it alongside Vite.
Remove/change "-d postgresql" if you are just testing or want to use other database.

rails new vrs-app --skip-javascript --skip-asset-pipeline -d postgresql

Install vite-rails as bundler

Next, install vite-rails as our bundler.

# add to gem file
gem 'vite_rails'

# install the gem
bundle install
bundle exec vite install

The install script will create some files for you, then you should see the success message.

Vite ⚡️ Ruby successfully installed! 🎉

If you want to know what is being installed, the installation process is explained in detail on the Vite website.
https://vite-ruby.netlify.app/guide/rails.html

Config Overmind for an easier development environment (Optional)

Now is also a good time to setup Overmind for our new project to save us some time.
If you wanted to know how to config Overmind for a Vite Rails project, read this tutorial.

Add hot-reload for erb files

First, since we will be using yarn, we can delete the unnecessary package-lock.json.
This file is used by npm, but since we are not using npm, we can safely remove it from our project.

Next, we will add hot-reload for erb files.

yarn add -D vite-plugin-full-reload

We will also update our vite config file.

/vite.config.ts

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import FullReload from 'vite-plugin-full-reload'  // add this line

export default defineConfig({
  plugins: [
    RubyPlugin(),
    FullReload(['config/routes.rb', 'app/views/**/*']), // add this line
  ],
})

Now when you update an erb file, the changes will be reflected on your browser.

Add Svelte

Now let's add Svelte with typescript support.

yarn add -D @sveltejs/vite-plugin-svelte @tsconfig/svelte svelte svelte-check svelte-preprocess tslib typescript

We will need to add one line to package.json manually:

/package.json

{
  "type": "module",
  "devDependencies": {
  ... ...
  }
}

This line tells vite our packages are ES modules.

Next, create these files at the root of our new Rails project:

/svelte.config.js

import sveltePreprocess from 'svelte-preprocess'

export default {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: sveltePreprocess()
}

/tsconfig.node.json

{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node"
  },
  "include": ["vite.config.ts"]
}

/tsconfig.json

{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "resolveJsonModule": true,
    "baseUrl": ".",
    /**
     * Typecheck JS in `.svelte` and `.js` files by default.
     * Disable checkJs if you'd like to use dynamic types in JS.
     * Note that setting allowJs false does not prevent the use
     * of JS in `.svelte` files.
     */
    "allowJs": true,
    "checkJs": true,
    "isolatedModules": true
  },
  "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.svelte"],
  "exclude": ["./public/**/*", "svelte.config.js",],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Finally, update vite config.

/vite.config.ts

import { defineConfig } from "vite";
import RubyPlugin from "vite-plugin-ruby";
import FullReload from "vite-plugin-full-reload";
import { svelte } from "@sveltejs/vite-plugin-svelte"; // add this line

export default defineConfig({
  //plugins: [RubyPlugin(), FullReload(["config/routes.rb", "app/views/**/*"])],
  plugins: [
    RubyPlugin(),
    FullReload(["config/routes.rb", "app/views/**/*"]),
    svelte(), // add this line
  ],
});

Your project root should look like this now:
vrs-1.png

Now we are ready to use Svelte in our Rails 7 project!

Note: All these files we have added are files created when running yarn create vite to create a Svelte project.
We were just copying these files manually here.

Try out Svelte

Let's try it out to see if Svelte really works.

Start by creating a Blog model.

rails g scaffold Blog title:string article:string
rails db:migrate

Create a Svelte component

Then, create a component for our blog page.

Note: in Vite Rails projects, components live inside app/frontend folder.
You can structure app/frontend anyway you want, but a components folder is most common.

app/frontend/components/BlogsShow.svelte

<script lang="ts">
</script>

<p>Hello Svelte</p>

Or if you do not use TypeScript:
app/frontend/components/BlogsShow.svelte

<script>
</script>

<p>Hello Svelte</p>

Create an entrypoint for the component

Next, we will create a file to load the component.

app/frontend/entrypoints/blogs-show.ts

import BlogsShow from "../components/BlogsShow.svelte";

new BlogsShow({
  target: document.getElementById("blogs-show"),
});

As you can see, this is simply a javascript file to mount components to HTML elements.
If you need more components, simply add more components to this file.

Tip: All files in app/frontend/entrypoints can be referenced with vite_javascript_tag or vite_typescript_tag.

If you do not want to use TypeScript, for this file, you can simply change the file extension from .ts to .js.

Referencing entrypoint files in an erb file

Vite provides vite_typescript_tag for referencing ts files.

For example

<%= vite_typescript_tag 'blogs-show'  %>

Will reference the file in app/frontend/entrypoints/blogs-show.ts.

So let's try it out. Add these tags to our old-fashioned, battle tested, Rails generated blog show page:

app/views/blogs/show.html.erb

<p style="color: green"><%= notice %></p>

<%= content_tag :div, "", id: "blogs-show" %>
<%= vite_typescript_tag 'blogs-show'  %>

<%= render @blog %>
...

If you do not want to use TypeScript, swap vite_typescript_tag with vite_javascript_tag.

Start the server, head to the blogs page, create a new blog post, and we should be able to confirm the Svelte component is working!

Passing props to Svelte components

When we need to pass data to a Svelte component, we can pass data just like a normal data attribute:

app/views/blogs/show.html.erb

<p style="color: green"><%= notice %></p>

<%= content_tag :div, "", id: "blogs-show", data: {title: @blog.title, article: @blog.article}%>
<%= vite_typescript_tag 'blogs-show'  %>
<%= render @blog %>
...

Then, in our component entrypoint, we can receive data like this:

app/frontend/entrypoints/blogs-show.ts

import BlogsShow from "../components/BlogShow.svelte"

const target = document.getElementById("blogs-show")

new BlogsShow({
  target: target,
    props: {
      blog: {
        title: target.dataset["title"],
        article: target.dataset["article"],
      },
    }
})
  • This file is app/frontend/entrypoints/blogs-show.js if you do not use TypeScript.

Then in the Svelte component, we can use the props just like normal.

app/frontend/components/BlogsShow.svelte

<script lang="ts">
    export let blog: {
        title: string;
        article: string;
    }
</script>

<h1>{blog.title}</h1>
<p>{blog.article}</p>

For non-TypeScript version:
app/frontend/components/BlogsShow.svelte

<script>
    export let blog
</script>

<h1>{blog.title}</h1>
<p>{blog.article}</p>

And that's it! We now have a fully functional Rails 7 project with Svelte integration.

Where to go from here

As you can see, the entrypoints serves as an entry point (duh) for our Svelte components.

There are a few strategies to expand from here.

Option 1. if you just want a traditional Rails application, and all you need is to just sprinkle Svelte components here and there, then you can definitely achieve that by simply adding a bunch of entrypoints for use in regular erb pages.

Option 2. is the middle ground - you may have a few entrypoints, e.g a blog entrypoint and an author entrypoint, then handle user interactions from inside these entrypoints.

Option 3. is to use a single entry point for all your resources and handle routing from JS - in other words, you will be creating an SPA.

All paths are possible, and it is entirely up to you to decide where to go!

Example Git repository

If you got lost somewhere in the tutorial, you can take a look at this GitHub repository for a complete code example.

https://github.com/planetaska/vrs-app

Credits

I didn't come up with this idea. I read this article by @yuki-n and found it very useful, so I created my own example to share the information.
https://qiita.com/yuki-n/items/bda13236d9b968e394e6