運用中のシステムへのVue.jsを導入する際のあれこれ


背景

JQueryで頑張るのが辛くなってきたので、
フロントエンドにもFW入れたくなってきた。
下記の点を比較し、最終的にVue.jsを導入することにした。

  • 学習コストの低さ
  • 部分的な導入が可能
  • FWとしての将来性

以下がその際に比較したまとめ。

前提条件

  • 運用中のシステム
    • rails4.1系
    • それなりの大きさのプロジェクト。しかもエンジニア一人か二人しかいないので、いきなり全取っ替えとか無理。
    • npm未導入
    • coffeescript使ってる
    • IE9以上対応
    • 非エンジニアも触るので、運用/開発の際の手間は増やしたくない
      • リリース時のステップ数増えるとか
      • 開発時に修正結果反映するまでのステップ数ふえるとか
  • 導入方法
    • 改修する周りから少しずつ。

導入の際に気を付けたこと

  • 今回はJSフレームワークの導入がメイン
  • ミニマムで導入していく
    • いきなり色んな事はしない
    • 既存のものはなるべく流用
  • componentでパーツ毎に分割する

使わないもの

  • npm
    • 悩んだが、gemから乗り換える運用コスト払ってまで導入するメリット見当たらず。
      • 結局vueとaxios以外入れないので
  • gulp
    • I love Sprockets!!!
  • browserify
    • 既存でこれ使わない呼び出し方でやってたので、これも使わない(後述)
  • vueify
    • Railsのviewを流用。vueファイル導入コスト払ってまで乗り換えるメリット見つからず。
  • ES2015
    • 今回はVue.jsの導入がメイン。これも導入するとなると学習コスト増えすぎ。
  • babel
    • ES2015使わないからこれも同様 とか

使うもの

  • coffeescript
    • 慣れてるのでまだ変えない。
  • Sprockets
    • やっぱり便利。まだ抜けられない。
  • gem 'vuejs-rails'
  • gem 'axios-rails'
    • vue-resource は引退で、axiosが公式推奨なんだって。
  • gem 'es6-promise-rails'
    • IEでpromise(axios)使えないって怒られた。その対応用。

導入事始め

ディレクトリ構成

.
├── app
│   ├── asset
│   │   ├── javascript
│   |  |   ├── ProjectA
│   |  |   |   ├── FuncA
│   |  |   |   |   ├── a.js.coffee
│   |  |   |   |   :
│   |  |   |   ├── FuncB
│   |  |   |   :
│   |  |   ├── ProjectB
│   |  |   :
│   |  |   └── frontend # Vue関連jsはここにまとめる
│   |  |       ├── ProjectA
│   |  |       |   ├── funcA.js.coffee # Vue.new
│   |  |       |   :
│   |  |       ├── ProjectB
│   |  |       :
│   |  |       └── components # Vue.component
│   |  |           ├── ProjectA
│   |  |           |   ├── FuncA
│   |  |           |   |   ├── componentA.js.coffee
│   |  |           |   |   ├── componentB.js.coffee
│   |  |           |   |   :
│   |  |           |   ├── FuncB
│   |  |           |   :
│   |  |          ├── ProjectB
│   |  |          :
│   │   ├── stylesheet # ここはvueで分けず共通
│   │   └── images
│   ├── view
│   |  ├── ProjectA
│   |  |   ├── FuncA
│   |  |   |   ├── a.html.erb # ここで対象となるvueのpartialをrender
│   |  |   |   :
│   |  |   :
│   |  ├── ProjectB
│   |  :
│   │   └── vue_template #vueのテンプレートはこちら
│   |      ├── ProjectA
│   |      |   ├── FuncA
│   |      |   |  ├── componentA.html.erb
│   |      |   |  ├── componentB.html.erb
│   |      |   |  :

.vueファイルは使わない。(導入の手間省く)
vue関連のファイルはディレクトリでなるべくまとめる

宣言/呼出

機能毎にVueをnewし、必要なコンポーネントを適宜呼び出すよう実装した。
本当ななるべく広い範囲をelementとしてVue.newしたかったが、
後述の問題点等により、ひとまず機能単位とした。
(★どの範囲をelementとするのがベストか。要調査)

funcA.js.coffee
$ ->
  class FuncAVue
    constructor: (hub) ->
      Vue.prototype.$http = axios
      new Vue(
        el: '#func_a'
        data: {}
      )

  window.funcAVue = ->
    hub = new Vue()  # コンポーネント間イベント通信するため
    componentAVue(hub)  # コンポーネントはここで宣言
    :
    new FuncAVue(hub)

vueのnewとコンポーネントは上記のようにclassで管理して、

a.html.erb
<script type="text/javascript">
  $(function(){
    funcAVue();
  });
</script>
:

erbでメソッドを実行することで呼び出す。
ちなみにコンポーネントは、

componentA.js.coffee
$ ->
  class ComponentAVue
    constructor: (hub) ->
      Vue.component('sample_component', {
        template: '#sample_component_template'
        data: ->
          return {}
      })

  window.componentAVue = (hub) ->
    new ComponentAVue(hub)

こんな感じ。

課題

handlebarsとの共存

handlebarsを利用しているのだが、こちらとデリミタが被ってしまっている。

new Vue(
    delimiters: ['${', '}']
:
)

でvueのデリミタを変更することで共存が可能となるはずなのだが、
なぜだかできない。要調査。

まとめ

今回、既存システムにVueをミニマムで導入してみた。
コンポーネントは、1フォームフィールド単位、1submitボタン単位などで作ってみた。

本当は相互に絡まない単位でコンポーネント化したかったが、
どうしても絡んでしまうようになってしまったため、
イベントハブやaxiosは途中で導入する事となった。
その辺こそ試行錯誤する事となった部分でもあるため、
また今度書きたい。

また、まだ導入初期のため効果の実感としては薄いが、
jQueryでガリガリ場合分けしていた部分等は見通しがスッキリしたので、
メンテナンスはかなりやりやすくなった。気がする。

参考