Introduction
Rails Hotwire is good but sometimes you want to build a more reactive. Vue.js is a good choice. In addition, Vue.js SFC is an awesome way to manage your components. However, you cannot just use .vue files because browsers don't recognize .vue files. They need to be compiled into JS codes first.
If you already know Vue.js, you probably know that using vite as the bundler to compile .vue files into JS codes is very common in the Vue.js world. There's a gem vite_rails to integrate vite into rails. I'll introduce how to configure vite_rails so you can use .vue files in Rails.
Preparation
If you want to start it from scratch(optional)
importmap is the default way to manage npm packages after Rails 7. However, we don't want to use importmap now, so add --skip-javascript behind the rails new command to opt out of all Javascript codes.
rails new you_project_name --skip-javascript
Of course, you can keep it if you want. It's just trickier to balance them carefully because they have kind of the same purposes.
Add a simple page
We add a simple index page just for demo
bundle exec rails g controller pages index
it will create the pages_controller an :index action
Install Vite
Add vite_rails in to the Gemfile
gem 'vite_rails'
and run
bundle install
bundle exec vite install
bundle exec vite install will add basic config file and inject some code snippet into the current view template, e.g. in the app/views/layouts/application.html.erb, it will inject these 2 lines
<%= vite_client_tag %>
<%= vite_javascript_tag 'application' %>
- 
vite_client_tag: is for HMR (hot module replacement) so you don't need to refresh the page to the the changes
- 
vite_javascript_tag: it will loadentrypoints/application.js
Run vite dev server
The vite dev server will transpile your code in real-time. It's required and also helpful for our development so that we can use features like HMR. Run this command in a new terminal session:
bin/vite dev
Test
To test if the vite_rails is install correctly, you can run rails server by
rails s
and go to http://localhost:3000/pages/index.
Open the console, if you see Vite ⚡️ Rails, then it means you have installed vite_rails successfully!
If you want to run the rails server and vite dev server at the same terminal session. You can install foreman and run foreman start -f Procfile.dev. Procfile.dev was also added by bundle exec vite install
Install Vue
Let's install 2 essential modules:
npm install vue
npm install @vitejs/plugin-vue
- 
vue: It's Vue.js, of course we need it
- 
@vitejs/plugin-vue: this module let vite recognize SFC and transpile.vueinto js
To enable @vitejs/plugin-vue, we need to configure vite. Open vite.config.ts and edit it like this:
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue' // <-------- add this
export default defineConfig({
  plugins: [
    RubyPlugin(),
    vue() // <-------- add this
  ],
})
It's done and we can try to use SFC in our app!
Mount your first Vue component
Let's create our first Vue SFC, App.vue, and mount it!
create App.vue
Create a file app/frontend/components/App.vue and edit it like this:
<template>
  <div>
    <h1>Vue App!</h1>
  </div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
  h1 {
    color: red;
  }
</style>
Note that different versions of vite_rails may have a different path for the source folder. The example here is app/frontend/components/App.vue, but your may be app/javascript/components/App.vue. It depends on where is your entries/appliaction.js
Set the mounting point
Then on our index page's erb, we replace everything with
<%# app/views/pages/index.html.erb %>
<div id="app"></div>
The SFC will be mounted on this div.
import App.vue in application.js
Open app/frontend/entrypoints/application.js. This is the entry point of the javascript program because we wrote <%= vite_javascript_tag 'application' %> in the layout.
import { createApp } from 'vue'
import App from '../App.vue'
const app = createApp(App)
app.mount('#app')
Import the App.vue and mount it
Test
Go to http://localhost:3000/pages/index, you should see the content of our App.vue!
Pass data from Rails to Vue components
Rails is very good at fetching the data in the database. You can make an API endpoint returning JSON string and let the Vue component to call that. However, if you just want the Vue components have the data as props when mounting it. For example, we want the App.vue show the text passed to it as props so we modify it as:
<template>
  <div>
    <h1>{{ msg }}</h1>
  </div>
</template>
<script setup>
defineProps({
  msg: String
})
</script>
<style lang="scss" scoped>
  h1 {
    color: red;
  }
</style>
Now our App.vue is expecting  props msg to be sent in.
We set a instance variable in PagesController:
class PagesController < ApplicationController
  def index
    @msg = "Hello Vue on Rails!"
  end
end
We then do a little trick here. Put @msg content on an empty div's data-* attribute, modify app/views/pages/index.html.erb
<%=
  content_tag(
    :div,
    id: 'appProps',
    data: {
      props: {
        msg: @msg
      }
    }.as_json
  ) {}
%>
<div id="app"></div>
it will render the div below on the page.
<div 
      id="appProps" 
      data-props="{"msg":"Hello Vue on Rails!"}">
</div>
We can then parse the data-props as a JSON in the application.js and sent the JSON to App.vue
import { createApp } from 'vue'
import App from '../components/App.vue'
const appProps = document.getElementById('appProps').dataset.props
const app = createApp(App, JSON.parse(appProps))
app.mount('#app')
(This is a trick used by https://github.com/gazay/gon)
Refresh the page and you can see the App.vue is showing our @msg
Conclusion
Vue.js SFC is a very powerful tool. To be honest, I was attracted to it the first time I saw its syntax. Combining HTML, CSS, JS into a single file is so elegant. Using Vue SFC with Rails is really a joy. Hope you like it!
 




 
    
Top comments (0)