Rails 6 Strategy To Use Vue Components And Dynamically Load Webpacker Assets

Dale Zak
4 min readMar 23, 2020

--

This article outlines some strategies how to use Vue components throughout your Rails app and utilize ERB to dynamically load components, filters, javascript, stylesheets and images for Webpacker. Sound too good to be true? Read on!

With Rails 6 adding Webpacker it’s easier than ever to integrate frontend javascript frameworks like Vue into your Rails app.

However the decision on whether to use a full javascript frontend or just sprinkle javascript components throughout your Rails app is still a bit confusing. The Vue On Rails book had some good insights on both approaches, but it still left a lot of unanswered questions for me .

Should you use pure *.vue components, or use *.vue.erb components? Should you still use the Asset Pipeline, or abandon it in favor of Webpacker? 🤔

After lots of research and investigation, I came to the decision to utilize Webpacker over Asset Pipeline, sprinkle Vue components throughout my Rails app and leverage ERB to dynamically load my components, filters, stylesheets and images for Webpacker. 💡

Create Rails Project

First I created the Rails project using Vue and Postgres, while skipping the Asset Pipeline since we’ll utilize Webpacker to load our assets instead.

rails new demo --git --skip-sprockets --webpack=vue --database=postgresql

Add ERB Webpack

We add the ERB webpack so we can dynamically load files server side into our application.

rails webpacker:install:erb

Add Node Packages

Next we’ll add some Yarn packages we’ll need in our components.

yarn add webpack webpack-cli pnp-webpack-plugin
yarn add turbolinks vue-turbolinks
yarn add rails-ujs activestorage css-loader
yarn add jquery bootstrap popper.js
yarn add axios vue-axios

Update Webpack Environment

Now update config/webpack/environment.js to the following.

const { environment } = require('@rails/webpacker')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')
const erb = require('./loaders/erb')
const webpack = require('webpack')
environment.plugins.append('Provide', new webpack.ProvidePlugin({
$: 'jquery',
jquery: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
moment: 'moment'
}))
environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
environment.loaders.prepend('erb', erb)
module.exports = environment

Add ERB Indexes

Since *.erb files are executed server side, we can use them to dynamically include components, filters, stylesheets and images in our application.

Add app/javascript/components/index.js.erb, this will include all your Vue components with *.vue extension.

import Vue from 'vue/dist/vue.esm';
<% components = Rails.application.root.join('app', 'javascript', 'components', '**', '*.vue') %>
<% Dir.glob(components).each do |path| %>
<% component = File.basename(path, ".vue") %>
import <%= component.underscore.camelize %> from "<%= path %>";
Vue.component("<%= component.underscore.dasherize %>", <%= component.underscore.camelize %>);
<% end %>

Add app/javascript/filters/index.js.erb, it loads all Vue filters with *.js extension.

import Vue from 'vue/dist/vue.esm';
<% filters = Rails.application.root.join('app', 'javascript', 'filters', '**', '*.js') %>
<% Dir.glob(filters).each do |path| %>
<% filter = File.basename(path, ".js") %>
import <%= filter.underscore.camelize %> from "<%= path %>";
<% end %>

Add app/javascript/javascripts/index.js.erb, this will import all secondary javascript files you may need.

<% javascripts = Rails.application.root.join('app', 'javascript', 'javascripts', '**', '*.js') %>
<% Dir.glob(javascripts).each do |file| %>
import '<%= file %>';
<% end %>

Add app/javascript/images/index.js.erb, it imports all your static images.

<% images = Rails.application.root.join('app', 'javascript', 'images', '**', '*.{png,svg,jpg}') %>
<% Dir.glob(images).each do |image| %>
import '<%= image %>';
<% end %>

Add app/javascript/stylesheets/index.js.erb, it imports all your custom stylesheets with *.css or *.scss extensions.

<% stylesheets = Rails.application.root.join('app', 'javascript', 'stylesheets', '**', '*.{css,scss}') %>
<% Dir.glob(stylesheets).each do |file| %>
import '<%= file %>';
<% end %>

Update Application.js

Lets rename app/javascript/javascripts/application.js to app/javascript/javascripts/application.js.erb so we can leverage ERB’s magic, then update it to the following.

import Rails from 'rails-ujs';
import Channels from 'channels';
import Turbolinks from 'turbolinks';
import * as ActiveStorage from 'activestorage';
import 'jquery';
import 'popper.js';
import 'bootstrap';
import 'bootstrap/dist/js/bootstrap';
Rails.start();
Turbolinks.start();
ActiveStorage.start();
import '../images/index.js.erb';
import '../stylesheets/index.js.erb';
import '../javascripts/index.js.erb';
console.log('Loaded Application');

Update Vue Pack

Since I don’t like the hello_ prefix convention, I renamed hello_vue.js to vue_pack.js.erb, then updated its contents.

import Vue from 'vue/dist/vue.esm';
import TurbolinksAdapter from 'vue-turbolinks';
import Axios from 'axios';
import VueAxios from 'vue-axios';

Vue.use(VueAxios, Axios);
Vue.use(TurbolinksAdapter);
import '../filters/index.js.erb';
import '../components/index.js.erb';
document.addEventListener('turbolinks:load', () => {
const element = document.getElementById('app');
if (element != null) {
const app = new Vue({}).$mount(element);
console.log("Loaded Vue", app);
}
});

Update Application Layout

To be able to sprinkle Vue components anywhere throughout our Rails app, in app/layouts/application.html.erb we’ll need to wrap the yield in a div so we can load Vue by the id.

<body>
<div id="app">
<%= yield %>
</div>
</body>

Also in app/layouts/application.html.erb, I added the following to the <head>.

<%= stylesheet_pack_tag 'application', preload: true, media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'vue_pack', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'erb_pack', 'data-turbolinks-track': 'reload' %>

Setup Foreman Procfile

Add Procfile in the root folder for production environment.

web: bundle exec puma -C config/puma.rb

Add Procfile.dev in the root folder for local environment.

web: bundle exec rails s -p 3000 
webpack: ./bin/webpack-dev-server

Start Your Foreman

Finally start your foreman, and blastoff! 🚀

foreman start -f Procfile.dev

Ok, now things are pretty well setup! It took some extra effort but it will be worth it.

We now have a Rails 6 app setup with Webpacker that dynamically loads your Vue components and filters, javascript, stylesheets, as well as your images. So in the future when you add a new component, stylesheets, etc it will be automatically loaded for you. You can also now use Vue components anywhere in your Rails app without worrying about having to import them.

I really like this approach over a full Vue frontend, because you can still utilize all the features of Rails you’ve grown to love like partials, helpers, routes, etc, while still using the power of Vue for more dynamic functionality.

What do you think of this approach? I’d love to hear from you in the comments!

Checkout my other Medium posts on Rails and Vue:

PS If you liked this article, please give it a few claps 👏

--

--

Dale Zak
Dale Zak

Written by Dale Zak

Full stack developer specializing in web apps built on Rails with Stimulus, and mobile apps using Ionic and Vue.

Responses (5)