ViteJS, Rails : a wonderful combination
Tags: rails, vite
Published: 2022-01-17 09:03:51

Article originally published here : https://www.bootrails.com/blog/vitejs-rails-a-wonderful-combination/

vite_rails is a fantastic gem that completely drops the need for jsbundling, Webpacker, importmaps and/or Sprockets.

## 0. Motivation

We are building [bootrAils](https://bootrails.com) 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  
  
```bash
$> 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

```bash  
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

```ruby
gem 'vite_rails'
```

Then in your terminal

```bash  
bundle install
bundle exec vite install
```

## 4. What vite_rails installed into your Rails app

### .gitignore was changed

```
# 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 :

```
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 : 

```erb
<%= 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

```js
{
  "devDependencies": {
    "vite": "^2.7.3",
    "vite-plugin-ruby": "^3.0.4"
  }
}
```

### vite.config.ts

```js
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.

```ruby  
# inside app/controllers/home_controller.rb  
class HomeController < ApplicationController  
end  
```

```ruby  
# inside config/routes.rb  
Rails.application.routes.draw do  
  get "home/index"  
  root to: "home#index"  
end  
```

```html  
<!-- 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.

```bash  
$/myapp> foreman start -f Procfile.dev
```

And open your browser 

<figure>  
<img style="display:block;float:none;margin-left:auto;margin-right:auto;width:60%" src="https://res.cloudinary.com/bdavidxyz-com/image/upload/v1640149358/rails/default_screen.png" loading="lazy" alt="Title and button">  
<figcaption style="display:block;float:none;margin-left:auto;margin-right:auto;width:60%">Title and button</figcaption>  
</figure>

Great ! 

## 6. CSS with Vite and Rails

create file *app/frontend/entrypoints/application.css*

```css 
// inside app/frontend/entrypoints/application.css
h1 {
  text-decoration: underline;
} 
```

change *app/views/layouts/application.html.erb*
```html 
<-- 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 :

```bash  
$/myapp> foreman start -f Procfile.dev
```

And open your browser at localhost

<figure>  
<img style="display:block;float:none;margin-left:auto;margin-right:auto;width:60%" src="https://res.cloudinary.com/bdavidxyz-com/image/upload/v1640150628/rails/underlined.png" loading="lazy" alt="Underlined">  
<figcaption style="display:block;float:none;margin-left:auto;margin-right:auto;width:60%">Underlined</figcaption>  
</figure>

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.

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

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

```js
// 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. 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 !