Vite + Vue + Bun on Rails

Published: Mar 5, 2024

Vue.js is one of frontend frameworks gaining popularity among rapidly emerging JavaScript technologies. The combination of Vue.js and Rails is becoming more popular as well, however, Vue.js development on Rails is not so straightforward. The reason would be that Vue.js relies on Vite for a development environment such as HMR (Hot Module Replacement) and bundling.

Since Rails 7, some JavaScript approaches have been supported. As of version 7.1.3.2, importmap (default), bun, webpack, esbuild and rollup are the choices. Vite is a replacement of such JavaScript approaches, but is not listed yet.

Here comes the vite_rails gem. The gem sets up the environment for Vite on Rails. Vite itself is independent from frontend frameworks. By adding a plugin, Vite + Vue development environment will be created.

One more addition in this blog post is Bun. Bun runs really fast. Here, Bun is used just a replacement of npm or yarn. However, Bun is a all-in-one toolkit and covers some features of Vite such as bundling. For the topic of Bun vs. Vite, the blog post, Why use Vite when Bun is also a bundler? - Vite vs. Bun, explains well. At this moment, Vite on Bun is an effective combination.

This blog post explains how Vue, Vite and Bun on Rails can be created. The source code is on the GitHub, rails-vite-vue.

Prerequisite

This blog is not about a big application, even though we need tools to be installed before getting started. Below is a list of what should be installed.

  1. Ruby: Installing Ruby
  2. Rails: Installing Rails
  3. Node.js: Installing Node.js via package manager
    or Download from https://nodejs.org/en
  4. Bun: https://bun.sh/docs/installation

Versions

  • Ruby 3.2.3
  • Rails 7.1.3.2
  • Node.js v21.5.0
  • Bun 1.0.29

Create a Rails App Skipping JavaScript

Rails supports importmap (default), bun, webpack, esbuild and rollup as JavaScript approaches. None of those will be used to transpile, bundle, or etc. to create a frontend by Vue. Bun will be used, but its role is a replacement of npm or yarn here. The best option is --skip-javascript. Also, --minimal option works if the app can be a simple one.

The command blow creates a Rails app without a JavaScript support.

$ rails new [APP NAME] --skip-javascript -T

Install Vite

The next step is to install Vite. Change a directory to the application, then type the command below.

$ bundle add vite_rails

The command above installs vite_rails gem along with a Ruby version of vite command. The Ruby version of vite command is used to install Vite and JavaScript version of vite command.

Now, it’s time to use the Ruby version of vite command. Type below.

$ bundle exec vite install

Above command does a lot. It installs the vite JavaScript package which includes JavaScript version of vite command. Also, it installs the vite-plugin-ruby JavaScript package. During the package installation, npm runs. It looks no option to switch to yarn or bun.

Additionally, it creates files listed below.

  • Procfile.dev
  • app/frontend/entrypoints/application.js
  • bin/vite
  • config/initializers/content_security_policy.rb
  • config/vite.json
  • vite.config.ts

Switching from npm to bun

Bun runs really fast, so this blog uses bun instead of npm. Since package-lock.json is no longer needed, delete the npm lock file.

$ rm package-lock.json
$ bun install

Once bun install is completed, Bun’s lock file, bun.lockb, will be created. After this, use bun command to install JavaScript packages.

Install Vue and Vue Plugin

We need Vue JavaScript package to develop Vue app. We also need the Vue plugin for Vite. Vite is a framework independent development tool. To use Vite for Vue development, Vue plugin should be installed and set up.

$ bun add vue @vitejs/plugin-vue

After the vue and plugin installation, edit vite.config.ts to set up Vue plugin.

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue' // added

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

Set up Starter Command

When the app was created, we skipped the JavaScript approaches. Because of that, the app doesn’t have a handy command such as bin/dev. The vite_rails gem created Profile,dev configuration for a foreman. This works, but, still, we need to type foreman start -f Procfile.dev to start the development server. It’s better to have the command, bin/dev.

package.json

Add the scripts section in package.json.

{
  "scripts": {
    "dev": "bunx --bun vite",
    "build": "bunx --bun vite build"
  },
  "devDependencies": {
    "vite": "^5.1.4",
    "vite-plugin-ruby": "^5.0.0"
  },
  "dependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "vue": "^3.4.21"
  }
}

The bunx command should be installed when Bun was installed. The bunx is a counterpart of npx.

Procfile.dev

Update the file as in below.

web: env RUBY_DEBUG_OPEN=true bin/rails server
js: bun run dev

This setting is to start two servers – one for Rails and another for a frontend.

bin/dev

Create a new file bin/dev with the contents below.

#!/usr/bin/env sh

if gem list --no-installed --exact --silent foreman; then
  echo "Installing foreman..."
  gem install foreman
fi

# Default to port 3000 if not specified
export PORT="${PORT:-3000}"

exec foreman start -f Procfile.dev "$@"

Then, change the file permission to executable. For example, chmod 755 bin/dev.

For now, we can start the two development servers by just typing bin/dev.

Create a Vue App Mount Point

Since it is a Rails app, a controller is responsible to receive HTTP requests.

$ rails g controller pages index

Edit app/views/pages/index.html.erb to add the mount point.

<%= content_tag(:div, "", id:"app") %>

Edit config/routes.rb so that the Vue app can be seen at a root path.

Rails.application.routes.draw do
  root 'pages#index'  # updated for a Vue app
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end

Create a Vue App

To make an app creation simple, the Vue app used here is the one create by bun create vite command. During the app creation, Vue and JavaScript was selected as a framework and language.

When vite_rails gem is used, an entrypoint file is app/frontend/entrypoints/application.js. The file is equivalent to main.js of the Vue sample app. Replace whole content of application.js (Rails) by main.js (Vite + Vue) or add entire main.js (Vite + Vue) to application.js (Rails).

Six files of Vite + Vue app below:

$ tree src public
src
├── App.vue
├── assets
│   └── vue.svg
├── components
│   └── HelloWorld.vue
├── main.js
└── style.css
public
└── vite.svg

4 directories, 6 files

should be mapped to below on Rails:

$ tree app/frontend
app/frontend
├── App.vue
├── assets
│   └── vue.svg
├── components
│   └── HelloWorld.vue
├── entrypoints
│   ├── application.js
│   └── style.css
└── vite.svg

4 directories, 6 files

How to organize directories/files under app/frontend looks not standardized yet. The vite_rails gem watches all files under app/frontend and reload if necessary. Above directory structure is just an example.

Start the App and Verify HMR

Start the servers by:

$ bin/dev

Open http://localhost:3000/ on a browser. The webpage below should show up.

img: vite + vue on rails

Try editing app/frontend/App.vue and app/frontend/components/HelloWorld.vue and verify HMR (Hot Module Replacement) is working.

References

Latest Posts

Application Development by Rails Action Cable

The previous two blog posts introduced WebSocket and how to implement a WebSocket application on Ruby on Rails. This blog post digs deeper. It is a memo on creating a more realistic application by Action Cable.

Real-time App on Rails by Action Cable

The previous blog post, WebSocket on Rails by Action Cable, focused on WebSocket as a protocol. As in the previous post, by default, Rails app responds to WebSocket connection requests without any hassle. However, other than connecting and sending ping frames, it doesn’t do anything. This blog post focuses on an application side and explains how we can create a full-duplex, bidirectional app.

WebSocket on Rails by Action Cable

In the web application domain, we hear some protocol names. Absolutely, HTTP or HTTPS is the most famous protocol that all web developers know. Although there’s a mechanism of Keep-Alive, a single request/response sequence with a single client/server is all done by HTTP. The client initiates the HTTP request to the server. Once the client receives the HTTP response from the server, communication finishes. As far as HTTP is used, the server just waits and waits. Only when the request comes in, the server can send back some data to the client. This communication style is surprisingly capable of doing many things, so most web applications are satisfied with HTTP.