DEV Community

Kevin Luo
Kevin Luo

Posted on

Use vite_rails to use Vue SFC(single file component, .vue) in >= Rails7

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

it will create the pages_controller an :index action

Install Vite

Add vite_rails in to the Gemfile

gem 'vite_rails'
Enter fullscreen mode Exit fullscreen mode

and run

bundle install
bundle exec vite install
Enter fullscreen mode Exit fullscreen mode

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' %>
Enter fullscreen mode Exit fullscreen mode
  • 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 load entrypoints/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
Enter fullscreen mode Exit fullscreen mode

Test

To test if the vite_rails is install correctly, you can run rails server by

rails s
Enter fullscreen mode Exit fullscreen mode

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!

See Vite ⚡️ Rails in the console

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
Enter fullscreen mode Exit fullscreen mode
  1. vue: It's Vue.js, of course we need it
  2. @vitejs/plugin-vue: this module let vite recognize SFC and transpile .vue into 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
  ],
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

Import the App.vue and mount it

Test

Go to http://localhost:3000/pages/index, you should see the content of our App.vue!

Our first Vue SFC

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

it will render the div below on the page.

<div 
      id="appProps" 
      data-props="{&quot;msg&quot;:&quot;Hello Vue on Rails!&quot;}">
</div>
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

(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

The App.vue with 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)