普段Vue.jsでフロントエンド開発するときの構成と運用時のポイント

Vue.jsでSPAを開発する際、そのプロジェクト構成に迷うかたは多いようです。
今回はそんなかたのために、私が普段Vue.jsで開発するときの構成と、その構成化で開発するときのポイントを共有したいと思います。

【2018/01/30追記】 まずは Nuxt.js の利用の検討を

Vue.js製のフロントエンド開発フレームワークである Nuxt.js がついに v1.0 のリリースを迎えました。
SPA開発とSSR + SPAの開発両方を全面的にサポートしており、PWA対応などもプラグイン一つでできる上、ルーティングの自動生成やVuexストアのオートロードも可能と非常に強力なフレームワークとなっております。
本格的なSPA開発を開始するときは、まずは Nuxt.js の利用を検討すると良いでしょう。

その上で、もう少しライトに使いたい場合にこれ以降の記事をご参考にして進めていただくのが良いかと思います。

https://nuxtjs.org/

GitHubサンプル

今回のプロジェクト構成のサンプルはGitHub上に公開しております。
MITライセンスを適用していますので、どなたでも自由に扱うことが可能です。
この記事単体で読むことも可能ですが、cloneしてプロジェクト構成を確認しながら閲覧することを推奨します。

https://github.com/potato4d/vue-dev-template

vue-cliを使った導入方法

2016/01/16 追記

@nakajmg さんの 「vue-cli用のテンプレートを作成する」(http://qiita.com/nakajmg/items/806b50ddd474dd7ecb4f) を基にvue-cli対応を行いました。
現行バージョンでは、vue-cliで以下のように叩くことでコマンド一つで導入が可能となっております。

terminal
$ vue init potato4d/vue-dev-template

基本的な開発環境

フロントエンド

Vue.js

今回は当然ながらVue.jsをメインで利用します。

Vue Router

ルーティング周りはVue Routerで処理します。
個人的には、特に沢山ページを生やさない場合でも、わかりやすさ重視で導入したいところではあります。

vue-loader

こちらは後述するwebpack内に組み込もうかと思いましたが、Vue.jsの開発構成に関わってくるのでこちらに記載しました。
vue-loaderを用いることで、Vueコンポーネントを.vueという単位でHTML, CSS, JS全てをファイル内のスコープに閉じ込めています。

HTTP: axios

HTTPリクエストにはaxiosを利用します。
axiosは、Nodeとブラウザ双方でそれぞれを意識せずに使えるライブラリであり、かつAPIコールに適した「ベースURLの設定」や、「JSONの最適化」などが行われています。

ビルドツール

ES to ブラウザJS: Babel

モダンJavaScriptの変換は勿論Babelで行っています。ルールとしては現在もっともメジャーなbabel-preset-envを利用。ポリフィルにはbabel-plugin-transform-runtimeを利用しています。

babel-preset-vue-app (2017/11/28追記)

babel-preset-vue-app は、 Vue 開発に必要な babel 設定を全てまとめて提供してくれるソリューションとなります。
具体的には、以下が含まれます。

  • babel-preset-env による最新のECMAScriptへの対応
  • 動的 import とスプレッド演算子のサポート
  • JSX in Vue のサポート
  • async/await および generator 構文のトランスパイル

https://github.com/vuejs/babel-preset-vue-app より

Vue 以外のフロントエンド開発においては、babel-preset-envを利用するケースがもっとも一般的ですが、
Vue においては公式に提供されている開発セットを存分に利用しましょう。

モジュールバンドラ: webpack

現在はビルドツールはwebpackに一任しています。
前述のvue-loaderが強力な他、webpack-dev-serverが便利ということもあり、愛用しています。

開発ユーティリティ

Packages: Yarn

パッケージマネージャにはYarnを利用しています。
npm v5からpackage-lock.jsonというロックファイルがnpmにもつきましたが、既にyarn.lockが十分に広まりすぎており、当分はYarnをベースとしたほうが良いという考えです。
また、Yarnは一度ダウンロードしたパッケージのキャッシュ、Native Extensionのビルドの並列化も提供します。開発の生産性向上にもYarnは欠かせません。

Lint: ESlint

LinterにはESLintを使用しています。
.vue ファイルに対してLintを行ってくれるプラグインがあるため、そちらを同時に導入しています。

StandardJS

ESLintの設定は、StandardJSを遵守しています。これは、現在のJavaScriptのStandardスタイルとして定義されており、Vueのエコシステム内でも非常に多く使われているものとなります。

DevServer: webpack-dev-server

開発中のサーバーにはwebpack-dev-serverを利用しています。
webpackとセットで利用できるのでビルド周りの設定を変えても良い感じにシュッとしてくれるので助かります。
browser-syncでも問題はないので、ここは好みで変えると良いでしょう。

Staging: Express

開発中にHerokuおよびHeroku Review Appsを利用する場合に使用しています。
Procfileに雑に記述して全てindex.htmlを参照する設定が可能なので、HerokuでNode以外のBuildpackを使いたくない場合にはベストでしょう。

プロジェクト構成

上記を全て盛り込んだ場合のサンプルの構成が以下となります。
次のセクションから、各項目を解説していきます。

.
├── .env
├── .babelrc
├── .eslintrc.js
├── LICENSE
├── README.md
├── package.json
├── public_html
│   └── index.html
├── src
│   ├── App.vue
│   ├── components
│   │   ├── Greeting.vue
│   │   └── globals
│   │       └── MainHeader.vue
│   ├── index.js
│   ├── pages
│   │   └── welcomes
│   │       └── index.vue
│   ├── routes
│   │   └── index.js
│   ├── stores
│   │   └── Stores.js
│   └── utils
│       └── Api.js
└── webpack.config.js

ルートのファイル/フォルダ群

.env .babelrc .eslintrc.js webpack.config.js

諸々の設定の記述に使います。

public_html/

ここに実際公開するファイル群を配置していきます。
また、webpack-dev-serverのcontentBase、S3アップロードの基点ディレクトリもここになります。

src/

開発のメインディレクトリとなります。

/src/内

index.js

基点となるスクリプトです。
ここでVue及びVue-Routerの初期化や、ルーティングの記述などを行います。
ここでは基本的に設定に属するもののみを行うこととし、ロジックは記述しないようにします。

App.vue

vue-routerのrouter-viewが書かれた簡素なテンプレートと、アプリケーション内全体で使用するCSSがあればここで記述します。

生のHTMLタグに対して適用したいスタイルなどはここに記述し、styleタグのscopedは使用しないで記述します。

components/

Vueのコンポーネントを記述します。
components直下には、ボタンやフラッシュメッセージなど、Atomic Designで言うところのAtomを定義していきます。

コンポーネントで行って良いこと

ここに配置するコンポーネントは、単体では副作用がなく、また、単方向のアプローチ(受け手側)のみを許可します。
ここに記述するスタイルは全てScoped CSSで記述し、また、データやクラスの扱いは全てpropsによって実現します。
この原則を守ることにより、内部で自己完結する、再利用性の高い仕組みの構築が可能となります。

components/globals/

ここにはWebサイト上でグローバルに展開されるものを格納します。
例えば全てのページで利用されるヘッダーコンポーネントや、ブログで言えば記事の末尾に付随するソーシャルボタン一覧といった、複数のページで利用されるページのパーツを記述していきます。

グローバルなコンポーネントで行って良いこと

propsではどうしても不十分なところも出てくるので、dataからのstoreの読み込みを許可します。
が、基本的にstoreへのデータ書き込みは禁止とします。

しかしながら、ヘッダーなどの場合、例えばアプリケーション内でのログアウト処理などが実装されることも考慮し、状態の変化のみは許可することとします。

例えばこれは、アクティブなページに関わる処理や、自身の状態に関わる処理などの、UI上での表示に関わる部分を中心とし、アプリケーションロジックに関わるデータへのアクセスは禁止するということです。

pages/

Vue-Routerのルーティングに対応するページのファイルを記述します。
基本的にルーティングとディレクトリ構成を統一するように作成します。

例として、/users/のルーティングのページファイルは、/src/pages/users/index.vueに記述します。
また、ファイル名は状況に応じて以下を利用すると良いでしょう。

/users/index.vue  - 基本となるページ(e.g. /users/ )
/users/detail.vue - 詳細ページ    (e.g. /users/1)
/users/new.vue    - 新規作成ページ (e.g. /users/new)
/users/edit.vue   - 編集ページ    (e.g. /users/1/edit)
ページのVueファイルで行って良いこと

基本的にこの構成においては、ページごとで行う挙動は全てページコンポーネントで行います。
storeの値を読み書きや、XHR/Fetch APIでのhttp通信、Vue-Routerの遷移などが該当します。

基本的に、「そのページ固有で何かを行いたい」という場合は、ページコンポーネントでするという考えで良いでしょう。

stores/

ここには複数のコンポーネントおよびルーティング内で共有するデータを保存します。
保存するデータに合わせてファイル名を決定します。

最低限のコードの場合、オブジェクトで書いても良いですし、場合によってはVuexの導入を検討すると良いでしょう。

utils/

ここには、複数のルーティング間で跨いで使いたい機能を記述します。
一般に公開されているnpmモジュールと同じ感覚で使いたいものは、全てここに記述すると良いでしょう。
サンプルでは、axiosをベースとしたAPIコールの抽象化としてApi.jsを置いています。

運用する時に破綻させない仕組み

ざっと構成と基本的な役割だけを記述しましたが、これだけではわかりにくいポイントや破綻しやすいポイントの管理方法をまとめて紹介します。

コンポーネントの動作に合わせて特定のJavaScriptを処理したい場合

基本的にコンポーネントは自己完結を前提とし、外部への副作用がないことが原則となるので、コンポーネント内で処理せず、グローバルコンポーネントでラップし、その中で完結させてやるか、もしくはページコンポーネントでディレクティブを設定してやる方法で解決しましょう。

素のコンポーネント上で下手に処理を増やすことは、予測可能性を損なうことになるので、出来る限り避けましょう。

どこでも使う便利CSS周りを定義したい

これも何かとページコンポーネントのstyleタグ上で定義したくなりがちですが、2回以上使用する場合はApp.vueもしくはコンポーネント内に記述すると良いでしょう。
これらの使い分けの基準は以下で決定しましょう。

それ自体が一つの要素として完結する

これは例えばボタンなどです。
当然ながら、これらはコンポーネントとして新たに定義しましょう。

.clearfixなどの便利機能

今では .clearfix は使いませんが、flexなどでも同様の便利クラスを利用することがあるでしょう。

いわゆるFLOCSSでいうutilityは全てApp.vue、もしくはcss-loaderを利用して外部CSSをロードする形でに記述しましょう。
ここの肥大化は避けるべきですので、基本的にこういった便利クラスの定義は避けるべきであるということを理解しましょう。
これらのクラスを利用することは、コンポーネントの疎結合という利点を破壊する行為ですので、利用の際は十分に注意しましょう。

HTMLタグそのもののデフォルトCSS

h1{}body{}といった根本に関する部分についても、App.vueに記述するのが良いでしょう。
また、あまりオススメはしませんが、public_html/index.htmlに書いてしまうのも手っ取り早い解決方法の一つとなります。

ページ間で毎回同じ部分を記述したい

この場合は、新たにutilityを生やすことにしましょう。
Vue.jsにはmixinの機能がありますが、個人的にはあまりオススメをしていません。
UImatterな問題はApp.vueにて共通化を、機能matterな問題は、utilityで解決することを推奨しています。

Atomic Designの「分子」に属するものを定義したい

これは個人的にも現状課題と感じている点です。
厳密にAtomic Designとして定義してしまうと幅がなくなってしまって窮屈である一方、コンポーネント思考のプログラミングというのは最終的にそこに限りなく近づくものだと考えているため、もうすこし「ゆるい」命名規則の必要性を感じています。
具体的には、 components に更に一階層増やし、原子、分子、有機体に相当するディレクトリを作成する方法を現在考えています。

おわりに

Vue.jsで開発する場合、コンポーネントやページなどで持たせる役割で迷われるかたが多いと思われますので、その解決策の一つとして、この方式を定義しました。

個人的にはFluxパターンなどで固めるより、ある程度の柔軟性がある構成のほうが何かと開発が円滑となるのでこのような構成を取ることが多いですが、あまりサンプルを見かけなかったので、コンポーネント/ページ単位で開発したい人の助けになればと思います。

また「◯◯の場合はどうする?」と言ったご意見をコメントで頂ければ、頂いた分だけ内容が充実できますので、疑問点などがあれば質問をいただけますと幸いです。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.