在 Rails 中開始 Vue
在三大主流前端框架中,最年輕的 Vue 無疑是個爆發力最強的一個,尤其是容易上手且中文資源多,對於很多人來說 Vue 是踏入前端框架的首選。Rails 自 3.1 版使用 jQuery 並且搭配 Asset pipeline 來打包前端資源已經好一段時間,在這個前端爆炸的年代這個組合已經略顯不足,直到 Rails 5.1 開始引入了 webpack 並且可搭配 yarn 做前端套件管理後,大大的提升 Rails 對現代前端框架支援的友善度! 本篇將會以一個搞不懂前端生態(誤) 的 Rails 開發者視角來探討:
- 手把手的寫一個簡單的 Rails 搭配 Vue 專案
- 如何在 Rails 中應用 Vue component
- 怎麼在 Rails 中搭配 Turbolink
- 其他有的沒的 (本篇使用的是 Rails 5.2.2)
專案初始
以下範例程式,我們將要做一個簡單的通訊錄系統,並且嘗試用 Vue 來顯示結果。 開始之前請先確定:
- 你的電腦上已經安裝好 node.js
- 你的電腦上已經安裝好 yarn. Mac user 可以直接使用 homebrew (
brew install yarn
) 安裝
Rails 利用 yarn 來管理前端套件,你可以把它想像成是一個比較新潮的 npm。
接下來要新增一個「預先裝好 Webpack 並搭載 Vue 而且不裝 Turbolink 的 Rails 專案」,我們命名這個專案為 「vue_example」
$ rails new vue_example --skip-turbolinks --webpack=vue
Turbolinks 是 Rails 用來加入頁面載入的套件,其實就是透過快取來替換部分的頁面內容,但是如果搭配使用前端框架渲染頁面時就有可能產生錯誤,我們之後再說明如何結合 Turbolink 使用 Vue,目前將 Turbolink 關閉。
雖然使用 rails s
啟動專案後就可編譯 webpack 的程式碼,但是可以使用 webpack-dev-server
來做即時的自動編譯(存檔後瀏覽器就會重新 hot reload)。Webpack 已經內建了 webpack-dev-server
,在 console 中輸入 bin/webpack-dev-server
就可以啟動了。但每次都要開兩個 server 也說不上很方便,所以可以順道安裝 foreman 來幫我們同時把需要的服務都開起來。
在 Gemfile 下新增 foreman
group :development do
# other gem ...
gem 'foreman'
end
存檔後記得 bundle install
接這在專案根目錄下新增一個 Procfile 檔案,把我們要一起開的服務都輸入進去
backend: bin/rails s -p 3000
frontend: bin/webpack-dev-server
存檔後,往後就可以在 console 中輸入 foreman start
來同時啟動所有需要的服務啦!
接著使用 scaffold 來快速產生我們要的範例程式
$ rails g scaffold AddressBook name:string email:string phone:string description:text
$ rails db:migrate
然後請自己在 rails console
下,自己新增一些假資料。
開始撰寫 Vue
我們來看一下安裝 webpacker 和 vue 之後, 在 app/
資料夾多下了個 javascript
資料夾,而底下的 path/
資料夾就是放置 webpack 的 entry point。講白話就是最後 webpack 的模組都會匯集到 entry point 裡,最後會利用神奇的魔法將模組編譯在一起,透過這個 entry point 來整合所有複雜的前端元素到我們的 html 網頁中。
/app/javascript
├── app.vue
└── packs
├── application.js
└── hello_vue.js
vue 幫我們自動產生了一個 entry point - hello_vue.js 和模組 app.vue ( 有興趣可以看一下裡面在寫什麼 )
我們這裡不使用 hello_vue.js,手動建立一個自己的 entry point - main.js
import Vue from 'vue/dist/vue.esm'
import NameCard from '../name_card.vue'
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
el: '#app',
components: { NameCard }
})
})
[app/javascript/packs/main.js]
然後新增一個叫做 name_card.vue 的元件檔
<template>
<div class="name-card">
{{message}}
</div>
</template>
<script>
export default {
data: function () {
return {
message: "this is a name card by Vue"
}
}
}
</script>
<style scoped>
.name-card {
border: 1px solid rgba(0, 0, 0, 0.205);
display: inline-block;
padding: 10px;
margin: 5px;
background-color: #fff;
border-radius: .25rem
}
</style>
接著我們要把 entry point 放到網頁裡面,最適合的地方就是在 Rails 預設的網頁 layout template: app/views/layouts/application.html.erb ,在它的<head>
標籤中加入 javascript_pack_tag
和 stylesheet_pack_tag
來載入 entry point
<head>
<!--...-->
<%= stylesheet_pack_tag 'hello_vue' %>
<%= javascript_pack_tag 'hello_vue' %>
</head>
[app/views/layouts/application.html.erb]
到 index.html.erb 頁面上,加入一個 <div id="app">
的標籤作為 Vue 的掛載點,並且把 NameCard
component放在裡面
<!-- other code ...-->
<hr>
<div id="app">
<name-card></name-card>
</div>
[app/views/address_books/index.html.erb]
存檔後啟動專案,打開瀏覽器看一下結果
其中 stylesheet_pack_tag
是用來載入 webpack 編譯完的 css style。可以試著把它拿掉,你會發現在 app.vue
內定義的 <scope style>
就失去了效果。
更好的 Vue component 掛載方式
如果覺得要使用 Vue component 都要先包一層掛載點像是 <div id="app">
覺得很麻煩(到底是多懶?),可以考慮以下的小技巧: 在 application layout 中,用一個標籤把整個 yield
包起來
<html>
<!--...-->
<body>
<div class="container" data-behavior="app">
<%= yield %>
</div>
</body>
</html>
[app/views/layouts/application.html.erb]
然後修改掛載點,綁定 [data-behavior]
屬性
// ...
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
el: '[data-behavior="app"]',
// ...
})
})
[app/javascript/packs/main.js]
這樣整個 view 都是我的 Vue 掛載點啦,是不是很方便! 這樣一來在 index.html.erb 中我們可以把 <div id="app">
拿掉,直接把 component <name-card>
放上去就好
使用 props 來收 Rails 的資料
接著要來繼續改善我們的程式,透過 Vue 的 props
屬性,可以接收由 component 外傳傳遞進來的資訊。 修改 name_card.vue
元件
<template>
<div class="name-card">
<ul>
<li>name: {{ item.name }}</li>
<li>email: {{ item.email }}</li>
<li>phone: {{ item.phone }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ["item"],
}
</script>
<!-- ... other code -->
[app/javascript/name_card.vue]
在 index.html.erb 中我們有一個 @address_books
是透過 Rails 取得的所有通訊錄資料,將資料個別取出後,轉成 json 格式就可以將每一筆的 address_book
餵給 Vue 的 NameCard
元件
<%@address_books.each do |address_book|%>
<name-card :item="<%=address_book.to_json %>"></name-card>
<%end%>
[app/views/address_books/index.html.erb]
關於 Vue 在 Rails 的自問自答
Turbolinks 一定要關掉嗎?
其實已經有相關的 Vue 套件 “vue-turbolinks” 可以讓 Vue 與 Trubolink 共存 使用 yarn 安裝 vue-turbolinks
$ yarn add vue-turbolinks
然後你的 entry point ( main.js ) 必須改成這樣
import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue/dist/vue.esm'
import App from '../app.vue'
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
const app = new Vue({
// ...
})
})
主要是因為 Turbolinks 會暫存到 Vue 渲染過的頁面,造成快取到錯誤的頁面結果,而這個套件就是在解決這件事情。
沒有了 jQuery, 有什麼可以替代 $.ajax 呢?
- ES6 原生的 fetch api
- Vue resource
- Axios : 目前 Vue 官方推薦使用
webpack 好麻煩,不能繼續用 asset pipline 嗎?
如果單純的在 <script>
中來撰寫 Vue,是不需要經過 Webpack 編譯的,可以自行將Vue.js library 引入到 application.js 裡面,或者直接安裝vuejs-rails gem。
如果我想要在 Vue 元件中用 i18n 或其他 Rails 的 helper 怎麼辦?
Vue 提供了 inline template 的方式,可以直接在我們的 Rails view file 中撰寫 vue 的模板,而不是去使用原本 .vue 中的 <template>
模板
<%# 我是 .erb %>
<name-card inline-template>
<%# 你可以在這邊使用 Rials helper %>
</name-card>
小結
本篇著重在如何在維持 Rails MVC 的架構下,輕量的導入 Vue.js 前端框架,對於 Rails 開發者來說是相對容易與現行架構整合的一個方式。如果對於 Webpack 想要更深入探索,推薦各位可以參考五倍紅寶石工程師 Roy 寫的 新手向 Webpack 完全攻略 系列文章,而看了本篇覺得 Vue 很有趣,歡迎來參加五倍紅寶石開設的 Vue.js 與 Vuex 前端開發實戰課程喔!