余り知られていませんが Nagisa ではアプリだけでなく Web のプロダクトやサービスもあります。マンガZEROUPTOON!月刊コミックジヘン 辺りがそうです。
何れも Vue.js で作られている SPA で、社内・外両方から “なんで Vue.js なの?” とかよく聞かれます。そこで、今回はどうして Vue.js を選択したのか、Vue.js の何がいいのか、Vue.js で運用してみてどうだったかの話をしたいと思います。

はじめに

Vue.js を導入する前のマンガ ZERO Web は 2.0系の Riot で作られていました。今ある SPA のような形ではなくサーバサイド (Go) にてメタタグを生成、空のマウントポイント
<div id="app"><div>
を持ったテンプレートを提供し Riot を使ったクライアントサイドのみのレンダリングです。
従来の jQuery 等に近い構成や1ページ1バンドルファイルで作れるのがメリットでしたが

  • 表示が一瞬もたつく
  • アプリケーションが大きくなるほどコンポーネントライフサイクルやビジネスロジックの管理が厳しく
  • ルータなど同じようなコード、ロジックがサーバ・クライアントに存在してしまうためメンテや仕様変更・追加が複雑化
  • 当時2.0系の Riot ではテンプレート構文の foreach を使った時の動作が致命的に遅い

などの問題を抱えていました。
リニューアルの動機は集客を考えたサイトマップの変更でしたが、同時に UX 及び技術サイドにも課題を抱えていたため、フレームワークや構成など含めた全面リニューアルとなりました。

それまで Nagisa で使ってきた JS フレームワークは React (14.x)、Angular (2.0ベータ)、Riot (2.x) の3つです。
Riot に関しては前述の理由から選択肢から除外しました。
残る選択肢の中から、業界スタンダードになりつつある React、型と DI の夢が詰まった Angular2 の中で決め兼ねていたところ、

など Vue.js の評判を頻繁に目にするようになり興味を持ったのが始まりです。
公式から vue-hackernews-2.0 というデモアプリが公開されていたので早速これをベースにプロトタイプを作ってみたところストレスなくいい感じに書けてしまったので、短い開発期間で初見のフレームワークを使うというリスクを取りながら導入に踏み切りました (Vue.js の2.x限定の話です、1.xはちょっと実用には厳しいかな…)。

Vue.js いいところ

1. スケーラビリティ

Vue.js 一番の特徴かつ魅力として、選択肢の広さがあります。例えば書き方において

  • 生の JS (ES5)
  • ビルド (Babel + Webpack, Browserify, Rollup)
  • TypeScript

など選択することができます。
これは Vue.js を開発した Evan You が提唱する Progressive Framework がベースにあるからで、Vue js the Progressive Framework – Evan You – Youtube の動画で詳細を聞くことができます。ざっくりした内容ですが

  • フレームワークは複雑さへの対処を助けるために設計されている
  • でもフレームワーク自体にも複雑さがある
  • フレームワークが複雑 (高機能) になればより複雑なアプリケーションの問題を解決できるようになるけど
  • アプリケーションの問題が小さければオーバースペックになって無駄な手続きとかが増えがち
  • だからアプリケーションの複雑さとフレームワークの複雑さのバランスが釣り合っていないとうまく機能しないよね

みたいな考え方で、この複雑さは様々で時に同じプロジェクト内でも変化していくし、常に複雑化していくだけの一方通行でもないとしています。Vue.js はフレームワークの複雑さを利用者がアプリケーションの実現したい課題の難易度に合わせて選択・調整できるように設計されています。これが The Progressive Framework の正体です。ここでいう複雑さやアプリケーションの課題は機能・仕様だけでなく

  • ツール
  • 設計手法
  • チームコラボレーション

なども含んだ総合的なものです。ちょっと意訳ぽくなってるので興味が湧いたら是非 YouTube の配信見て欲しいなと。


出典: The Progressive Framework – Google Slide

しかも Vue.js はアプリケーションの複雑さに対応するためよりフレームワークのディープな機能 (上記の画像で右よりの More Features) に進んだとしても、フレームワーク固有の複雑さ (Framework Complexity) が比較的少なく、相反する要素が絶妙にバランス取りされています。
アプリケーションに合わせて機能を選択でき、Less → More も More → Less の何方も可能な 2 way であること、これが Vue.js を使う上での大きなメリットとなってきます。

私達の場合 Vue, Vuex, Babel (ES2017), PostCSS, Pug, SSR を取り入れていますが、Webpack の設定をゴリる必要もなく (vue-loader への依存は強いですが) 設定も学習も簡単ながら必要な複雑さに対する解を得ることができました。

2. 書きやすさとスピード

書きやすさというとふんわりした言葉ですが、ここでは

  • 冗長な記述が少ない
  • フレームワーク独自の規格やルールが少ない
  • 学習コストの低さ

といった点を軸において考えます。書きやすさはフロントエンドエンジニア以外の人がマークアップや CSS コーディングをする時の助けや、開発スピードにも繋がるとも考えています。

2-1. Single File Component

Vue.js の特徴として Single File Component (SFC) と呼ばれるものがあります。これは .vue という拡張子のファイルに HTML・CSS・JS をまとめて書けるもので、この様な書き方になります。
SFC の書き方で全てインラインで書くことも、 src アトリビュートとしてパスを指定し、ファイルを分割することも可能です。このアプローチは Riot によく似ていて、従来の Web や Web Components の考え方から外れずに実現されています。
lang アトリビュートを追加すれば Pug、SASS、TypeScript などのようなメタ言語の対応も簡単です。
個人的に Angular 程綺麗な実装だとは思いませんが、よりシンプルに多くのケースをカバーできるようになっているのが魅力です。

2-2. Template

Vue.jp のテンプレートは v-if 文や v-for などのディレクティブを備えた HTML ベースのテンプレート構文と JSX、HTML (DOM) をテンプレートとして扱う3つのパターンから選択可能です。
JSX 以外の選択肢があるのはデカいですね。Vue.js のテンプレートは公式で言われている通り

仕様に準拠しているブラウザや HTML パーサによってパースできる有効な HTML

です。テンプレートは Angular にとても良く似ていて、表現に困ることはまず無いでしょう。
今のところ .vue ファイルインラインのみのサポートですが、eslint-plugin-vue が4.0からテンプレート構文もチェックできるようになったので、これが充実していけば JSX (React) や Angular で便利なテンプレートのエラー検知が可能になります。

出典: Custom Elements Everywhere

Web Components フレンドリーなのも見逃せないところです。

2-3. Style

Vue.js は ScopedCSS をサポートしています。各コンポーネントは固有の data アトリビュートを持ち、.cover[data-v-7dbc67f4] のように CSS のクラスと対応付けて Scoped を実現しています。テンプレート同様これも Angular way ですね。
SFC 上では前項のように scoped を付けるだけの Mozilla 風な書き方をします。

子コンポーネントのルート要素に親要素のスタイルが効く という挙動がよく嫌われますが、Custom Elements のように親コンポーネントから子コンポーネントにスタイルをあてることができるので、例えば Atomic Design の Molecules から Atom を利用し、マージンを与えるようなケースに有効です。
また、各コンポーネントがユニークな名前を持ち、それに対応した CSS クラス設計ができていれば問題にはならないでしょう。
以前 CSS Module を採用を検討したこともありましたが、Webpack 設定にどっぷり浸かる上にコードとテンプレートを汚染されるのが悩みでした。Vue.js に限りませんが Scoped CSS は非常にクリーンで書きやすく、メンタルへの負担が無くなります。

3. アーキテクチャ

柔軟で学習コストが低く書きやすいと言うと、とっつきやすいだけのファッションフレームワークと思われるかもしれません。なので、ちゃんと書けます! という例を幾つか紹介したいなと思います。

3-1. Component

Vue.js はコンポーネント側に Reactive 機構を持ち、Computed Properties (結果のキャッシュ) や Watchers (常に再計算) で変更検知と再計算をします。Props や Application State の変更をライフサイクルで検知して必要な処理を発火させる、という面倒な手順は必要なくなり、Subscribe している Prop や Application State、Route 情報などの変更から変更に関係のあった処理だけが自動的に走る、という考え方でよくなります。
Vue.js のライフサイクルはシンプルですが、これにより表現に困ることはまず無いでしょう。

出典: The Vue Instance – Vue.js
React の最適化でよく話題になる shouldComponentUpdate が Vue.js に不要なのも、Reactive による変更検知の恩恵です。冗長な記述がシンプルになり、開発スピードとメンテナンス性を向上させながら同等のパフォーマンスが得られます。

3-2. vue-router

Vue.js 公式のルータです。やはりこれもシンプルで使いやすいなって感じです。私の周りでは「Named Routes が使いやすい」という声を聞いたことがあります。

私が気に入っているのは beforeRouteUpdate という hook です。ここで Fetch 処理を済ませておくことでページ遷移時に Window が高さを失うことなく、コンテンツがある状態で次 (or 前) のページのレンダリングに移ることができます。scrollBehavior と組み合わせることで、ブラウザの進む・戻るアクションを起こされてもスクロール位置をストレスなく復元することができます (知るのが遅かったせいで Nagisa のプロダクトにはまだ活かせていませんが…)。
文章よりコードで見たほうが分かりやすいと思います。これこのへんですね。
React や Angular の Router 事情にあまり詳しくなく、それぞれこのへんをどうやってるのか知らないのでもしかすると同じことが同じくらい簡単にできるかもしれません、その時はそっと教えて貰えるとうれしいです。

勿論 vue-router 以外のサードパーティ製ルータと組ませることも可能です。

3-3. Vuex

Vuex という Vue.js 専用の 状態管理パターン + ライブラリ があります。
Flux パターンに近くState、Action、Getter、Mutation (Setter) 4つの機能が提供されています。
Action が非同期の Promise を返す前提で作られているため、Redux でよく起こるミドルウェアに振り回される事はまずありません。また前述の通り Vue.js 側に Reactive を備えているので Vuex を引き剥がして別のフレームワークに入れ替えよう、となってもコンポーネント側の Watch や Computed は Obserbable な State さえ提供されれば影響はなく、せいぜい dispatch 周りのみです。この点は React + MobX よりもModel と View・ViewModel の分離がいいとも言えそうです (そもそも React は MVVM ではないので正確ではありませんが便宜上)。

ただし Vuex はドメイン層に getter があるとか、view から mutation 呼べちゃうなど行儀の悪い部分もありますのでしっかりルールを設けたほうがいいです。その部分さえ整備してしまえば綺麗に見通しよく組めると思います。Vuex の設計パターンについては Vue+VuexでMVVMなWebApplicationを設計するときに考えたいこと がすごく参考になります。

出典: What is Vuex?

Vue.js よくないところ

いい点を挙げてきましたが、勿論ダウンサイドもありました。

  1. 型安全な世界はまだ厳しい (TypeScript)
    リニューアルにおいて型定義に Flowtype は余り本質的ではないかな (リニューアルや本格的なコードリプレイスなく導入できるのが強みだから) と思うのでここでは TypeScript だけを対象にしていますが、特に Vuex の構造から公式ライブラリだけで型安全なアプリケーションの構築はまだ難しいです。コミュニティメンバーにより積極的に試行錯誤されている段階なので、これからだと思います。

  2. エコシステム
    Material 系のコンポーネントライブラリは充実していますが、カルーセル、スライダー、ジェスチャー等々エコシステムは React に比べて弱いです。あとちょっと手を加えてくれれば、、、みたいなことはしばしば起こります。PR 送ろうにもあんまり活発に開発されていなかったりも。
    なのでコンポーネントは自分で作ったほうがいいなってことが多々あります。作って公開したいですね。最低限必須になりそうな vue-metavue-analytics は実用レベルで使えるので安心してください。

  3. ネイティブアプリ
    React には ReactNative があります。このブログのReactNativeでiOSとAndroidの漫画アプリを同時に作るでも書かれている通り実用レベルにまで成熟していますが、Vue.js の Weex はまだ発展途上です。
    Nagisa はアプリの会社なので問題になりませんが、そうではない環境においてこれは大きなマイナスになるかもしれません。

  4. ロゴ
    非常に主観的ですがロゴがダサいのでモチベーションに少し悪影響です。

実際運用してみてどうだったか?

Nuxt.js という強力な存在 (React でいう Next.js) がありますが、自社プロダクトのようなケースだとフルコントロールが必要だったので使っていません。
とはいえ vue-hackernews2.0 というデモアプリがあるのでこれをベースに作っていくだけで結構使えたりします。
これベースにサービス作った時のあれこれを LT にしたスライドがあるのでもし興味があれば見てみて下さい。

運用時の負荷やパフォーマンスに関しては、去年の公開初期は安全策を取ってミドルウェアでページャッシュを採用していました。その後v2.5辺りから安定・パフォーマンスも向上し、現在はアプリケーションキャッシュ (Node.js の LRU) だけで動いています。とはいえまだユーザ数の少ない状態なので、今後ユーザを伸ばしていった時の負荷とログ周りは課題になりそうだなという感じです。Node.js で SSR した時は他のフレームワーク同様、CPU の稼働率が一番のネックになります。
これをコンポーネントキャッシュや functional component、再利用性の低いコンポーネントを分割させないように、などでどの程度まで低くできるのか? 今後検証、チューニングしていきたい部分です。 (正直コンポーネント減らすのはかなり効果ある…)。

まとめ

  • アプリケーションの複雑さに合わせてフレームワークの機能・複雑さを選択・調整できる
  • その多くが独自の拡張へ走ることなく既存の Web 技術の上に成り立っている
  • Vuex と Router を始めとする公式ライブラリがよくできているのでそれなりの規模になっても辛くない
  • ライフサイクルと変更検知が優秀
  • スケールアップダウン・記法において柔軟性が非常に高いので将来の心配が少ない
  • SSR はユーザ増えてどうなるか

という感じで導入は正解でした。またこれだけしっかり使える FW ながらファイルサイズが Angular の10%、React の60%程度で Vue.js が最軽量なのも嬉しいポイントです。

因みに、もともとメタタグはサーバ側で生成していて且つコンテンツはクライアントサイドのレンダリングでも Search Console にてちゃんとレンダリングできていたこともあり、SSR によって SEO が強くなるという事はありませんでした。あくまでアプリケーションを散らかさないようにメタタグや OGP を生成できるようにしたらついでに SSR もできちゃって、Initial Load がもたつかなくなってハッピー! みたいなノリでいいんじゃないでしょうか。

一つ一つを見れば他のフレームワークでも実現可能なものですが、その網羅性と拡張性を最小限の独自のルールに抑えながら、既存の技術の上に乗せたところが最大の強みです。
もしその考えや設計思想に共感、或いは恩恵を得られるプロジェクトがあれば、積極的に Vue.js の採用をおすすめします。
最後に、この記事が FW 選定に迷っている人とかの役に立ったらいいなと思います! ありがとうございました。

出典・関連