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.
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
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
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.
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.
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:
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.
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
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>
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
.
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!
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"],
},
}
})
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.
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!
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
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