Post

ViteJS, Rails, a wonderful combination

0. Motivation

We are building BootrAils on top of the last Rails version. When Rails 7 came out, it was (kind of) a relief to see a replacement for Webpacker. Whereas the new assets pipeline looks better, it seemed that room for even better could exist somewhere.

The current frontend assets management of Ruby-on-Rails could feel hacky for many developers :

  • [live/auto/hot] reloading of HTML pages still doesn’t work natively,
  • Serving assets with Rails has changed multiples times, from nothing (Sprockets appeared with Rails 3.1), to not-completely-ready importmaps. Currently Rails support jsbundling, that wraps esbuild (or optionally another tool…), before being injecting again to Sprockets. “importmaps” are optionally supported on top of this. And css bundling works another way…

What if you want a proper, unified, elegant, performant assets frontend management right from the start ?

There’s a gem for that

1. Prerequisites

Check that you have ruby 3 already installed. Check you also have bundler installed, and npm above version 7

1
2
3
4
5
6
7
8
$> ruby -v  
ruby 3.1.0p0 // you need at least version 3 here  
$> bundle -v  
Bundler version 2.2.10
$> foreman -v 
0.87.2
$> npm -v
7.1.0 // you need at least version 7.1 here 

Any upper versions should work.

2. Create a new minimalistic Rails application

1
2
3
4
5
6
7
mkdir railsvite && cd railsvite  
echo "source 'https://rubygems.org'" > Gemfile  
echo "gem 'rails', '7.0.1'" >> Gemfile  
bundle install  
bundle exec rails new . --force --css=bootstrap --minimal
bin/rails db:create
bin/rails db:migrate

3. Install vite_rails

Open the Gemfile, and add

1
gem 'vite_rails'

Then in your terminal

1
2
bundle install
bundle exec vite install

4. What vite_rails installed into your Rails app

.gitignore was changed

1
2
3
4
5
6
7
# Vite Ruby
/public/vite
/public/vite-dev
/public/vite-test
node_modules
*.local
.DS_Store

Ok, not much surprises here. Vite.js is here to handle npm assets, so node_modules is safely ignored. What’s inside public/ is technically required by vite_rails, so far we don’t have to know what it is about.

.Procfile.dev

Since Rails 7, you need foreman to start your local server with ./bin/dev. This command actually calls foreman under the hood, looking for the local .Procfile.dev

vite_rails overrides the default one as follow :

1
2
vite: bin/vite dev
web: bin/rails s

Ok, the frontend part runs separately from the server.

app/frontend/entrypoints/application.js

Wow ! Magic starts to happens right now. Instead of a separate directory for javascript, and other kind of assets, everything is now managed from app/frontend. That makes things a lot clearer. The content of this file is mostly docs, and a “hello world” from the console, so we won’t investigate it right now (but you can do it, for curiosity).

app/views/layouts/application.html.erb

The interesting part is here :

1
2
<%= vite_client_tag %>
<%= vite_javascript_tag 'application' %>

vite_client_tag : Renders the Vite client to enable Hot Module Reload, according to the docs.

vite_javascript_tag ‘application’ : well, at first sight, this tag should include the javascript generated by vite.

So no more unclear javascript and stylesheet inclusion : now Vite rears everything for us.

package.json

1
2
3
4
5
6
{
  "devDependencies": {
    "vite": "^2.7.3",
    "vite-plugin-ruby": "^3.0.4"
  }
}

vite.config.ts

1
2
3
4
5
6
7
8
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'

export default defineConfig({
  plugins: [
    RubyPlugin(),
  ],
})

What we can understand from vite.config.js is that we will profite from vite.js itself. So all docs from Vite.js are profitable for our project. See how nicely the RubyPlugin is included. It doesn’t litter our configuration, and left all

Vite itself is included. Nothing hidden then, the vite-ruby-plugin is here to bridge the gap between Vite and the Rails application (tag helpers…)

Other files changed

Less interesting, but worth mentioning : package-lock.json is changed (obviously), config/initializers/content_security_policy.rb is also changed for security reasons, and bin/vite is added to start vite itself when launching a local server.

4. End of the first-half ⚽

That’s it ! Just by having a sneak peek at the code, everything seems elegant and intuitive from the start. Perfect ! 👌

5. Create minimal files

Ok, so let’s test each of the expectations. First, we need the bare minimum files.

1
2
3
# inside app/controllers/home_controller.rb  
class HomeController < ApplicationController  
end  
1
2
3
4
5
# inside config/routes.rb  
Rails.application.routes.draw do  
  get "home/index"  
  root to: "home#index"  
end  
1
2
3
4
5
6
<!-- inside app/views/home/index.html.erb -->
<h1>This is h1 title</h1>  
  
<button>
   This is a button
</button>  

These 3 files are enough to trigger a default view. Let’s try.

1
$/myapp> foreman start -f Procfile.dev

And open your browser

Title and button
Title and button

Great !

6. CSS with Vite and Rails

create file app/frontend/entrypoints/application.css

1
2
3
4
// inside app/frontend/entrypoints/application.css
h1 {
  text-decoration: underline;
} 

change app/views/layouts/application.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
<-- inside app/views/layouts/application.html.erb -->
<html>
  <head>
    <title>Myapp</title>
    <%= vite_client_tag %>
    <%= vite_javascript_tag 'application' %>
    <!-- add line below, remove old stylesheet_link_tag -->
    <%= vite_stylesheet_tag 'application' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Now stop your local server

Restart it :

1
$/myapp> foreman start -f Procfile.dev

And open your browser at localhost

Underlined
Underlined

We’ve just seen how Vite is able to handle stylesheets : just add an entrypoint in the well-named app/frontend/entrypoints folder, and reference it with the vite_stylesheet_tag helper in your layout.

7. Hot reloading with Rails and Vite

“Hot reloading” means that the browser will automatically refresh itself, each time you change/save anything in your code editor.

For those who already used anything like React/Vue/whateverJSframework, it sounds already pretty obvious, because pressing “f5” into the browser each time is tedious when you have to write dozens of lines of code every day.

But believe it or not, it still doesn’t apply to the default Rails app, even for the recent Rails 7 version.

Let’s try it, when Vite is installed.

Hot reloading CSS

Your localserver should be started. Comment and save the entire app/frontend/entrypoints/application.css file, and open your browser. The style of the title should have disappeared. Now uncomment and save the entire app/frontend/entrypoints/application.css file. The style of title should be underlined in your browser, automagically.

Hot reloading just works ! 🎉🎉🎉

Hot reloading JS

Try the same experience with app/frontend/entrypoints/application.js. You should see changes instantly happen in your browser.

Hot reloading HTML

If you try to change and save app/views/home/index.html.erb. What happened in your browser ? nothing. Uh-oh.

It’s time to see how to add a plugin to ViteJS.

8. Add a plugin to ViteJS, with Rails

Hopefully for our HTML, ViteJs has a plugin dedicated to this task.

1
$/myapp> yarn add -D vite-plugin-full-reload

Quickly check the plugin was installed in package.json, then open vite.config.ts

1
2
3
4
5
6
7
8
9
10
11
// inside vite.config.ts
 import { defineConfig } from 'vite'
 import RubyPlugin from 'vite-plugin-ruby'
 import FullReload from 'vite-plugin-full-reload'
 
 export default defineConfig({
   plugins: [
     RubyPlugin(),
     FullReload(['config/routes.rb', 'app/views/**/*'], { delay: 200 })
   ],
 })

Relaunch your local server. Try to change and save app/views/home/index.html.erb, now any change to the view automagically appears in your browser !

9. Credits

10. What a gem !

It’s already a long tutorial, so that’s it for today, but there will be more articles about the powerful Vite.

We have seen how ViteJS completely replaces the default Rails frontend assets management, in an intuitive manner.

Then we’ve seen how hot reloading works, and how to add a Vite plugin to our application to achieve a nice development workflow.

Enjoy !

This post is licensed under CC BY 4.0 by the author.