Let's make cool boy Vue even cooler on Rails!
Add this line to your Rails application's Gemfile
:
gem 'vue_cli-rails'
And then execute:
$ bundle install
- Ruby >= 2.3
- Rails >= 4.2
- Node >= 8.9+
- Optional:
yarn
-
Feel free to use
yarn
ornpm
. -
Single
vue_entry
rather than confusingjavascript_packs_with_chunks_tag
,stylesheet_pack_tag
,javascript_packs_tag
, etc. -
Get all benefits of Vue CLI.
- Powered by
webpack
4 - DRY: all-in-one configuration file rather than repeating for
webpack
,eslint
and etc. - Out-of-box tooling: Babel, TypeScript, PWA,
vue-router
,Vuex
, CSS pre-processors, linter and testing tools. - Enhanced alias support in
jest.config.js
.
- Powered by
-
Run
webpack-dev-server
together with Rails server in dev mode. -
Just single
RAILS_ENV
, no moreNODE_ENV
. -
Rails way configurations in
config/vue.yml
.
Out-of-box workflow:
-
Make sure
@vue/cli
already installed globally vianpm
(npm i -g @vue/cli
) oryarn
(yarn global add @vue/cli
) -
bundle exec rake vue:create
and follow the steps.Don NOT select
In package.json
for "Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?". Some functionalities like alias of jest may not work. -
Put your JavaScript files under
app/assets/vue/entry_points
. -
Insert your entry point by
vue_entry 'entry_point'
in views orrender vue: 'entry_point'
in controllers. -
webpack-dev-server
auto starts alongsiderails server
in dev mode. -
Invoke
env RAILS_ENV=production bundle exec rake vue:compile
to compile assets (you still must manually setRAILS_ENV
toproduction
).
More settings are available in
config/vue.yml
The root path of your Vue assets is app/assets/vue
. This gem will generate several folders. However, app/assets/vue/entry_points
is the only one matters.
The entry path is configurable in
config/vue.xml
.
Webpack sees one JavaScript file as the center of a web page rather than HTML. Thus all styles, images, fonts and other assets are related to a JS files by import 'css/png/svg/woff2/json'
. Any .js
file under app/assets/vue/entry_points
will be a entry-point.
Please ONLY puts your entry-point files under entry folder with .js
extension name.
Be aware,
.js.erb
and.vue.erb
are NOT supported. I will explain the reason in Q&A section.
If you are new to modern front-end development, or more specifically with webpack
in this case, please read Q&A section for more information.
vue_entry
is like javascript_include_tag
and stylesheet_link_tag
which generates relative assets links for your entry point. (It's like javascript_packs_with_chunks_tag
in Webpacker 4. I will explain why it's different in Q&A.)
You may have interest of path alias in
config/vue.yml
.
Usually you only need <div id="app"></div>
and vue_entry 'entry/point'
to render a Vue page. You can use render vue: 'entry/point'
inside your controller.
This method is simply a wrap of render html: vue_entry('entry_point'), layout: true
. So you can pass any arguments supported by render
including layout
.
For example
# app/controllers/my_vue_controller
class MyVueController < ApplicationController
layout 'vue_base'
def foo
render vue: 'foo/bar'
end
end
<!-- app/views/layouts/vue_base.erb -->
<!DOCTYPE html>
<html>
<head>
<title>My Vue</title>
</head>
<body>
<div id="app"></div>
<%= yield %>
</body>
</html>
// app/assets/vue/entry_points/foo/bar.js
import Vue from 'vue';
import Bar from '../views/Bar.vue';
Vue.config.productionTip = false;
new Vue({
render: h => h(Bar),
}).$mount('#app');
If the default setting vue_assets
does not bother you at all, you can ignore this section.
Actually public_output_path
in config/vue.yml
is very simple - just a sub path under public
directory. You might suffer some problems by changing it without understanding how it works:
- My regular assets no longer work in dev mode server.
- I lost all my files in
public
folder. (Using a VCS would get your ass saved.) - Where are my compiled assets for prod under
public/assets
directory?
TL, DR - DO NOT name it as any path used by anything else
-
It's being used in:
- Rails dev server will forward all request under
/#{public_output_path}
towebpack-dev-server
; - Webpack will put all compiled assets under
public/#{public_output_path}
. Unfortunately, it always remove the entire folder before compiling.
- Rails dev server will forward all request under
-
Alternative ways
- For dev proxy problem: to set different values for
development
andproduction
mode inconfig/vue.yml
. - For deleting folder when set it to
assets
for prod: runrake vue:compile[with_rails_assets]
to invokerake compile:assets
as well.
- For dev proxy problem: to set different values for
If you still feel confusing, please create a new project and select copy demo code.
I will explain what happens in Explanation by Demo.
-
entry_path
Entry point folder. Default:
app/assets/vue
-
manifest_output
Where to put
manifest.json
which required by Rails production mode. You can set it in development mode for inspection.All entry-points will be compiled into assets files. Rails needs
manifest.json
to know what are the files and will serve all its JS/CSS tags. -
package_manager
Pretty straightforward, which package manager will be used. Valid value:
npm
oryarn
. It does NOT supportpnpm
or other package managers. You can find the reason in Q&A. -
public_output_path
Because it is very important I put it in core section.
-
launch_dev_service
(NOT available forproduction
mode)rails server
will launch it when starting by defaultvue-cli-service serve
. It will be invoked bynpx vue-cli-service serve
oryarn exec vue-cli-service serve
depends on yourpackage_manager
. -
camelCase
settings will be used invue.config.js
Please see available options.
-
alias
It's basically
resolve/alias
for Webpack. However, you don't have to config this settings in.eslintrc.js
andjest.config.js
again and again.@vue/cli
will pass the settings to eslint via its plugin. The configuration for jest will be generated and passed tojest.config.js
throughvue.rails.js
.
Feel free to update vue.config.js
by yourself. There are some lines of boiler-plate code to adapt compression-webpack-plugin
and webpack-bundle-analyzer
.
-
vue:create
Install required packages and configurations. You should run this task to get
@vue/cli
initializing your project.What it does for you
-
Select package manager: Y=
yarn
, N=npm
- Directly use npm if yarn has not been installed.
- Prefer yarn by default unless detect
package-lock.json
-
Auto install
@vue/cli
globally with your package manager. -
Invoke
vue create
to initialize Vue project.When detected existing
package.json
Y
- Yes: Fully overwriteN
- No: SkipA
- Auto: You won't loss anything (old_config.merge(new_config)
)K
- Keep: Keep all your settings already have (new_config.merge(old_config)
)
-
Install
js-yaml
andwebpack-assets-manifest
-
Deleting Vue demo code under
src
folder -
Copy demo code to under
app
folder and updateconfig/routes.rb
-
Copy
vue.rails.js
andvue.config.js
- Do not change
vue.rails.js
! This rake task will always restorevue.rails.js
back. - Yes you can update
vue.config.js
. Just make sure you know what are you won't break the configuration. You can chanceconfig/vue.yml
instead.
- Do not change
-
Generate
config/vue.yml
- The convention is:
camelCase
for regularvue.config.js
,snake_case
for special usage. - You can find a full list of Vue CLI config options below.
- All available options here
- The convention is:
BE AWARE: the default option for
config/vue.yml
isY
(to replace existing file), otherwise your package manager change won't be saved. All your files won't be overwritten silently exceptvue.rails.js
. -
-
vue:compile
Compile Vue assets. Please specify
RAILS_ENV=production
to compile assets for production.Optional argument:
[with_rails_assets]
to invokerake compile:assets
after it finished.However, you can invoke
vue-cli-service build
(ifvue-cli-service
installed globally, or you can usenpx vue-cli-service build
oryarn exec vue-cli-service build
) withRAILS_ENV=production
to build assets.A good practice is to use
cross-env
to passRAILS_ENV=production
. Socross-env RAILS_ENV=production vue-cli-service build
will work on any platform and shell. -
vue:json_config
Converts
config/vue.yml
to JSON to be used byvue.rails.js
.vue.rails.js
prefers parsingconfig/vue.yml
withjs-yaml
. So this is just in case. You may suffer performance issue when your Rails app grow big. -
vue:support[formats]
Adds template or style language support. Vue ships with supporting
pug
,slm
,sass
,less
andstylus
out-of-box. How ever, you still have to install some loaders manually if you did not select that language withvue:create
.You can add multiple languages at the same time:
rake vue:support[pug,stylus]
-
vue:node_env
Adds
cross-env
andnpm-run-all
to yourdevDependencies
inpackage.json
, and addsdev
,prod
,serve
andrails-s
toscripts
as well.It enables you to start rails dev server alongside
webpack-dev-server
without pain, and compile production assets.# to start `rails s` and `webpack-dev-server` together npm run dev # or yarn dev # same as `/usr/bin/env RAILS_ENV=production bundle exec vue:compile` npm run prod # or yarn prod
You can update
scripts/rails-s
and/orscripts/prod
if you need to more stuff:{ "scripts": { "rails-s": "cross-env NO_WEBPACK_DEV_SERVER=1 rails s -b 0.0.0.0", "prod": "cross-env RAILS_ENV=production bundle exec rake vue:compile[with_rails_assets]" } }
-
vue:inspect
Alias of
vue inspect
,npx vue-cli-service inspect
oryarn exec vue-cli-service inspect
. Display the webpack configuration file.
You may need to invoke
rake
withbundle exec
. Rails 5 and above supports newrails rake:task
flavor.
It's very easy to migrate from Webpacker.
- Install this gem and
bundle install
- Install
@vue/cli
globally and follow the instructions ofrake vue:create
; - Edit
config/vue.yml
, setdefault/entry_path
tosource_path
(by defaultapp/javascript
) joinssource_entry_path
(by defaultpacks
); - Change all
javascript_packs_with_chunks_tag
tovue_entry
; - Fix all nonsense
xxxx_packs_tag
; - If you mind
public_output_path
andmanifest_output
, you can change them to follow Webpacker values;I strongly NOT recommend to put
manifest_output.json
underpublic
folder; - Update
vue.config.js
if you have any customized webpack configurations;You can inspect webpack settings at anytime with
rake vue:inspect
orvue inspect
- Directly
rails s
to start dev server;You can get rid of
bin/webpack-dev-server
andbin/webpack
now. However, still recommendrake vue:node_dev
and runyarn dev
so it will killwebpack-dev-server
properly when your Rails dev server stopped. - Call
env RAILS_ENV=production rake vue:compile[with_rails_assets]
instead ofenv RAILS_ENV=production rake assets:precompile
to compile all assets for production. - Delete unused Webpacker files
bin/webpack-dev-server
bin/webpack
config/webpack
config/webpacker.yml
Strongly recommend to backup your codebase before the migration.
Enjoy Hot Module Replacement now!
You can check the full list on Vue CLI official website.
-
Special
-
publicPath - see
public_output_path
-
outputDir - see
public_output_path
-
configureWebpack -
vue.rails.js
will generate it.entry
,output
andresolve/alias
are heavily required by this gem. So you must manually update it invue.config.js
very carefully.Demo
Changes to
vue.config.js
const { manifest, pickUpSettings, // isProd, - // getSettings, + getSettings, } = railsConfig; + const { configureWebpack: { entry, output, resolve, module: cwModule } } = getSettings('configureWebpack'); module.exports = { ...pickUpSettings` outputDir publicPath devServer - configureWebpack filenameHashing lintOnSave runtimeCompiler transpileDependencies productionSourceMap crossorigin css parallel pwa pluginOptions `, + configureWebpack: { + entry, + output, + resolve, + module: cwModule, + }, chainWebpack: (config) => {
-
-
Supported
- filenameHashing
- lintOnSave
- runtimeCompiler
- transpileDependencies
- productionSourceMap
- crossorigin
- css
- devServer
- parallel
- pwa
- pluginOptions
-
Unsupported
- baseUrl - Deprecated
- assetsDir - ignored
- indexPath - N/A
- pages - N/A
- integrity - N/A
- chainWebpack - directly edit
vue.config.js
-
My dev server can't find assets
Sometimes your
webpack-dev-server
might still be running while Rails dev server had terminated. For example, you had executedexit!
inpry
.Usually
webpack-dev-server
should be killed when next time you startrails server
. However, for some reason it could fail and the newwebpack-dev-server
will listen to another port. Then you must manually kill them:lsof -i:3080 -sTCP:LISTEN -Pn kill -9 <pid>
Alternatively, you can run
rake vue:node_dev
and always start your dev server with:npm run dev # or yarn dev
I know it is not Rails-way at all. I don't want to waste time to reinvent it - you are already using Node, why it bothers you?
-
My API does not work with CSRF token
Because Vue does not have opinion of Ajax (or JSON API) preference, you must implement what
jquery-ujs
does by yourself. There is an example in vanilla JS with querySelector (which should work for IE8+) and Fetch API:async (url, data) => { const $csrfParam = document.querySelector('meta[name="csrf-param"]'); const $csrfToken = document.querySelector('meta[name="csrf-token"]'); const csrfParam = ($csrfParam && $csrfParam.getAttribute('content')) || undefined; const csrfToken = ($csrfToken && $csrfToken.getAttribute('content')) || undefined; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-type': 'application/json', 'X-CSRF-Token': csrfToken, }, body: JSON.stringify({ ...data, [csrfParam]: csrfToken, }), }); if (!response.ok) { // handle bad response } return response.json(); } catch (e) { // handle failed case } }
Alternatively you can turn off CSRF token and set SameSite cookie if all your clients no longer use IE. Modern browsers can handle
SameSite
flag to prevent CSRF attacks. -
My Jest complains about
import
Seems
transformIgnorePatterns
injest.config.js
not working the same way in different environments. I found sometimes<rootDir>/node_modules/
works while/node_modules/
works on another machine. Try to change the order and you will find which one works for you. -
Mocha tests not working
This is a known issue and I am not going to fix it recently.
-
TypeScript can not find my aliases
This is a known issue. TS is still using
tsconfig.json
rather than a.js
or.ts
file. You must manually update it for now. I will try to find a way out. -
My
yarn test:...
/npm run test:...
not working properlyThe test requires setting
RAILS_ENV=test
. You can invokerake vue:test[unit]
rake vue:test[e2e]
instead. -
Got errors like
command "..." does not exist
forrake vue:lint/test
This rake task simply invokes
vue-cli-service test:...
. Those commands will be generated by some vue plugins. It won't be available unless you got correct plugin installed.
Q & A
Only webpack-assets-manifest
is a required dependency. It will be used to generate manifest.json
which required for both dev and prod.
vue.rails.js
uses js-yaml
for parsing config/vue.yml
. It will fallback to rake vue:json_config
if js-yaml
not been installed. However, when your Rails app grow bigger, you will very likely find rake tasks start slower and slower.
Short answer I don't know and I don't recommend. There are several HAML packages but all are too old. JS world suggests pug. You can also use slm if you prefer Slim. Both are quite similar to CSS selector syntax, which means you don't really need to spend time to learn.
Just rake vue:support[pug,slm]
and try them out: <template lang="pug">
or <template lang="slm">
.
No.
The reason is Vue CLI does not support pnpm
very well, or npm
does not support @vue/cli
. Who knows.
You still have a chance to get it worked by giving pnpm --shamefully-flatten
flag, which makes no difference from npm
or yarn
.
If someday they support each other, I'd like to support pnpm
as well.
Yes I admit it. Personally I'd like to directly write SPA with webpack tooling for front-end. Back-end will be a separated project, and it will be a Rails-API or Sinatra project if I want to use ActiveRecord.
webpack-dev-server
can simply be configured with a proxy and I can use something like npm-run-all
to start 2 services at the same time. I had to write some not-so-good code to get those things done in Rails.
The demo is more Rails way - separated layouts and views. SPA world uses some client router like vue-router
.
Sorry, I don't think many gems work on Windows. Please install a virtual machine and run Linux on it. This gem is very likely working with WSL
, however you may suffer performance issues due to its file system
Currently vue.config.js
is reading configurations from vue.rails.js
which depends on js-yaml
. It will fallback to bundle exec rake vue:json_config
without js-yaml
installed. Your rake tasks may spend a minute to load which apparently is not a good idea.
I do want to support SSR. However, the way Vue officially provided requires you to write separated code for client and server then compile with Webpack, which is quite complicated. prerender-spa-plugin
might be easier to achieve it.
I will do more investigation like how Nuxt.js does SSR. But I can't guarantee anything now.
-
Webpacker is designed more generic.
vue_cli-rails
is opinionated to just get@vue/cli
worked. -
Due to the ease of use of
@vue/cli
this gem is much easier to configure. -
This gem does not support
.erb
files at allIt does not make any sense to me. What's the benefit? What if someone wants to pass dynamic data in production, which the way
erb
supposed to work for? Should it launch webpack and spend maybe 10s to compiled it on-the-fly?If you want some dynamic data, you should fetch it via some API. That's my opinion and why I don't even think it should have this feature.
-
This gem only provides 1 helper
vue_entry
rather thanstylesheet_pack_tag
,javascript_pack_tag
,javascript_packs_with_chunks_tag
,image_pack_tag
and etc. in Webpacker- First of all, JS files are the center of modern front-end world.
- Modern front-end bundlers like webpack offer lots of fancy features with tools like Babel, TypeScript, eslint and etc. They can take care of all your assets, and always give you correct set of assets.
- Webpack 4 is even smarter to split common code into chunks.
- Vue also provides some awesome features like scoped styles, async components.
All those functionalities are just working out-of-box. You should not even touch them. There is no point of managing assets by yourself. You will eventually shot on your foot:
- Why my component does not look right? Because you forgot to change your
stylesheet_pack_tag
after renaming the file. - Why my styles from another component aren't working? Because your component uses scoped styles, which designed to only work for that component.
- Why my component does not work anymore? Because there is a new asset and you never write a
_tag
for it.
Trust me, you are not smarter than webpack. This design will save your time.
-
This gem supports both
npm
andyarn
. Webpacker requiresyarn
to be installed.npm
andyarn
are not much different nowadays. I myself preferyarn
. But you should be able to usenpm
which ships with node itself to reduce the complexity of deployment.Unfortunately it does not support
pnpm
-
Webpacker ships with plenty of node dependencies in
dependencies
section rather thandevDependencies
.I'd say this is another thing does not make sense to me. Even there is no real difference for front-end projects, I'd expect a project follows common pattern: the packages will be used in your front-end code should be in
dependencies
. -
productionSourceMap
is off by default for productionYou may or may not know Rails turn this flag on by default.
I just don't buy it. It could be a security issue, especially for a startup or small company where Rails is widely being adapted. There are plenty ways if you intended to open your source, like Gitlab does. You should not do unaware contribution.
You can manually set
productionSourceMap
totrue
inconfig/vue.yml
. Good on you! -
This gem puts
manifest.json
inapp/assets/vue
directory by default thanpublic
.Again, I have no idea why Webpacker doing that.
-
webpack-dev-server
automatically starts withrails server
in dev mode.I don't understand why not to start the killing feature of webpack. Stop wasting your life on stupidly refreshing and waiting the whole page being reloaded while debugging front-end code.
If your computer is too slow, ask your boss to buy a good one. You deserve it.
-
Less configurations and easier to understand.
Only a few platform-specific settings available. All others are very standard.
Copy Demo Code
Run bundle exec rake vue:create
or rails vue:create
in Rails 5+, and follow the steps:
$ bundle exec rake vue:create
Which package manager to use? (Y=yarn, N=npm) [Yn]
...
? Generate project in current directory? Yes
...
? Check the features needed for your project: Babel, Linter, Unit
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
...
Do you want to copy demo code? (Y=Yes, N=No) [yN]y
...
Now with rails s
, open http://localhost:3000/vue_demo/foo
in a browser you should be able to see a red big red "Foo" with blue "Hello Vue!".
Do not stop your rails server, open app/assets/vue/views/Foo.vue
in your editor:
<template>
<div id="app">
<h1>Foo</h1>
- <HelloWorld msg="Vue!"></HelloWorld>
+ <HelloWorld msg="Rails!"></HelloWorld>
</div>
</template>
Change msg="Vue!"
to msg="Rails!"
and save. You will the text in your browser changed to "Hello Rails!". You can change styles or edit app/assets/vue/components/HelloWorld.vue
and immediately see the result as well.
This functionality is called HMR (Hot Module Replacement) which is the killing feature provided by webpack-dev-server. You will soon fail in love with this feature and never want to go back to manually refresh your browser again and again.
.
├── app
│ ├── assets
│ │ └── vue
│ │ ├── components
│ │ │ ├── HelloWorld.vue
│ │ │ └── layouts
│ │ │ ├── App.vue
│ │ │ └── index.js
│ │ ├── entry_points
│ │ │ ├── bar.js
│ │ │ └── foo.js
│ │ └── views
│ │ ├── Bar.vue
│ │ └── Foo.vue
│ ├── controllers
│ │ └── vue_demo_controller.rb
│ └── views
│ ├── layouts
│ │ └── vue_demo.html.erb
│ └── vue_demo
│ └── foo.html.erb
├── config
│ ├── routes.rb
│ └── vue.yml
├── tests
│ └── unit
│ └── example.spec.js
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── babel.config.js
├── jest.config.js
├── package.json
├── postcss.config.js
├── vue.config.js
├── vue.rails.js
└── yarn.lock
You can run ESLint by
$ yarn lint # npm run lint
Run Jest
$ yarn test:unit # npm run test:unit
First let's compile the assets
$ env RAILS_ENV=production bundle exec rake vue:compile
run: yarn exec vue-cli-service build
...
File Size Gzipped
public/vue_assets/js/chunk-vendors.b54 82.49 KiB 29.80 KiB
85759.js
public/vue_assets/js/foo.dcbad15e.js 2.74 KiB 1.23 KiB
public/vue_assets/js/bar.d4fc59af.js 2.03 KiB 1.00 KiB
public/vue_assets/css/foo.4bbe6793.css 0.12 KiB 0.11 KiB
public/vue_assets/css/bar.96de90a8.css 0.02 KiB 0.04 KiB
Images and other types of assets omitted.
...
Your file names could be different from mine. Don't worry, we won't look those files. There are the files you will get:
.
├── app
│ ├── assets
│ │ └── vue
│ │ └── manifest.json
└── public
└── vue_assets
├── css
│ ├── bar.96de90a8.css
│ └── foo.4bbe6793.css
└── js
├── bar.d4fc59af.js
├── chunk-vendors.b5485759.js
└── foo.dcbad15e.js
Let's take a look at app/assets/vue/manifest.json
:
{
"bar.css": "/vue_assets/css/bar.96de90a8.css",
"bar.js": "/vue_assets/js/bar.d4fc59af.js",
"chunk-vendors.js": "/vue_assets/js/chunk-vendors.b5485759.js",
"entrypoints": {
"bar": {
"js": [
"/vue_assets/js/chunk-vendors.b5485759.js",
"/vue_assets/js/bar.d4fc59af.js"
],
"css": [
"/vue_assets/css/bar.96de90a8.css"
]
},
"foo": {
"js": [
"/vue_assets/js/chunk-vendors.b5485759.js",
"/vue_assets/js/foo.dcbad15e.js"
],
"css": [
"/vue_assets/css/foo.4bbe6793.css"
]
}
},
"foo.css": "/vue_assets/css/foo.4bbe6793.css",
"foo.js": "/vue_assets/js/foo.dcbad15e.js"
}
As mentioned above, there are 2 files under app/assets/vue/entry_points
folder: foo.js
and bar.js
. They will become entry points in manifest.json
. When you call render vue: 'bar'
in VueDemoController
or <%= vue_entry('foo') %>
in app/views/vue_demo/foo.html.erb
, vue_entry
will look for them in entrypoints
of manifest.json
, then generate <link href="<path>" rel="stylesheet">
and <script src="<path>"></script>
for each asset.
It's slightly different on dev server. This gem will send requests to webpack-dev-server and fetch the paths on-the-fly. The request will be GET http://localhost:3080/__manifest/?<entry_point>
for each vue_entry
. You can also send GET request to http://localhost:3080/__manifest/?!!INSPECT!!
to get the full manifest.