Since the web is moving away from building interactive web pages with plain jQuery, I figured it’s time to incorporate one of the new javascript frameworks into a Rails project I’m working on. I’ve tried making sample applications using Ember and React, but found both frameworks to have relatively steep learning curves. Vue.js, on the other hand, was simple and straightforward to add to a sample application.
In fact, I could include a link to CDN just like I could with jQuery. There was no need to install transpilers or other system dependencies. Because all I’m looking for is a library to allow me to add some interactive components (e.g. nested forms, wizards, and interactive selections), Vue.js feels like the right tool for the job. For more reasons to choose Vue.js over the alternatives, you may want to check out this post.
Webpacker is still relatively new to Rails (v5.1+), so I wanted to share how I resolved a couple gotchas that I ran into when setting up my Rails project that uses both Turbolinks and Vue.js.
Getting started
Create a new Rails project:
$ rails new vue_example_app --webpack=vue
Or if you have an existing Rails app that has Webpacker installed run:
$ bundle exec rails webpacker:install:vue
This creates an app/javascripts
directory where you will put your Vue components, but these components won’t be add included in the asset pipeline, so you’ll need to add them to your application layout file.
<!-- app/views/layouts/application.html.erb -->
<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>
Once you’ve added that, be sure to start up the webpack dev server with:
$ bin/webpack-dev-server
For simplicity you may want to consider a tool like Foreman to manage spinning up both the Rails development server and the webpack server with a single command.
# Procfile
web: bundle exec rails server -p \$PORT -e \$RACK_ENV
webpack: bin/webpack-dev-server
Be sure to explicitly state the RACKENV in a .env file or Rails will complain about not being provided with a secretkey_base. To do this, add the following:
# .env
RACK_ENV=development
When you go to a page rendered by this Rail application you should see the following message emited on the javascript console:
Hello World from Webpacker
This message is being generated in app/javascript/packs/application.js
. Now, that we have webpacker working, let’s intialize a basic Vue.js application.
Initializing Vue.js
Add the following line:
// app/javascript/packs/application.js
import './hello_vue.js'
This will append a message of “Hello Vue” to any view you render, but the message will not appear again if you navigate away, while using Turbolinks. We’ll fix this in the next section. To keep things organized, I recommend move app/javascripts/packs/app.vue
into a separate app/javascripts/components
directory. Once you do be sure to revise update the references in hello_vue.js
// Original
import App from './app.vue'
// Revised
import App from '../components/app.vue'
After making this change, you’ll need to restart the webpack dev server.
Configuration
Depending on your use case, it may or may not make sense to use Vue and Turbolinks together. If you’re building one page application, you want to opt for vue-router instead. For my application, I’m just adding a few Vue components for some added interaction. To make Vue and Turbolinks work side by side, I’m using vue-turbolinks
. To install this package, run the following and then restart your webpack dev server:
$ yarn add vue-turbolinks
Once you’ve installed vue-turbolinks, you need to enable it.
// app/javascripts/packs/hello_vue.js
import Vue from 'vue'
import App from '../components/app.vue'
import TurbolinksAdapter from 'vue-turbolinks'
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
document.body.appendChild(document.createElement('hello'))
const app = new Vue(App).$mount('hello')
console.log(app)
})
Now, that we have code triggered whenever we navigate using Turbolinks, let’s create a div
to bind our Vue application to. For sake of example, let’s use a DOM element with id=hello
.
// app/javascripts/packs/hello_vue.js
import Vue from 'vue'
import App from '../components/app.vue'
import TurbolinksAdapter from 'vue-turbolinks'
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
var element = document.getElementById("hello")
if (element != null) {
var app = new Vue({
el: '#hello',
template: '<App/>',
components: { App },
})
}
})
At this point, nothing happens even if the necessary div
is present. This is because Vue package default to only including the “runtime-only” build, not the “standalone” build, which is necessary to target elements in existing HTML templates. If you’re interested in learning more about the differences, click here. Fortunately, the fix for this is simple:
// Old
import Vue from 'vue'
// New
import Vue from 'vue/dist/vue.esm'
Now everything should work as expected. Hope this helps you get up and running.