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.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
],
})
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)