Vue.jsでSPAを開発する際、そのプロジェクト構成に迷うかたは多いようです。
今回はそんなかたのために、私が普段Vue.jsで開発するときの構成と、その構成化で開発するときのポイントを共有したいと思います。
追記
-
2019/12/05 追記
- 時代が大きく変わって求められる構成なども変化しました。
- 現在ではこの記事は参考にしないことをおすすめします
-
2018/01/30 追記
- まずは Nuxt.js の利用の検討を
- Vue.js製のフロントエンド開発フレームワークである Nuxt.js がついに v1.0 のリリースを迎えました。SPA開発とSSR + SPAの開発両方を全面的にサポートしており、PWA対応などもプラグイン一つでできる上、ルーティングの自動生成やVuexストアのオートロードも可能と非常に強力なフレームワークとなっております。本格的なSPA開発を開始するときは、まずは Nuxt.js の利用を検討すると良いでしょう。
- その上で、もう少しライトに使いたい場合にこれ以降の記事をご参考にして進めていただくのが良いかと思います。
- https://nuxtjs.org/
はじめに
時勢によって変わっている情報などが存在するため、読み進める前にいかの情報をご一読ください。
TODO:
以下は今後対応予定の項目です
-
prettier の項目対応
- ESLint との兼ね合いの追記
-
Netlify の項目対応
- Heroku の項目の粉砕
-
Jest +
@vue/test-utils
の項目対応
GitHubサンプル
今回のプロジェクト構成のサンプルはGitHub上に公開しております。
MITライセンスを適用していますので、どなたでも自由に扱うことが可能です。
この記事単体で読むことも可能ですが、cloneしてプロジェクト構成を確認しながら閲覧することを推奨します。
Vue CLI 3.x について
【2018/07/04 追記】
現在では Vue.js オフィシャルの CLI ツールがオールインワンな環境を提供しており、手動での環境構築が基本的に不要となっています。
個人としては SPA 開発であれば Nuxt.js ベースの環境構築を強くおすすめしますが、本記事は Vue.js 単体での環境が対象となるため、今後 Vue CLI 3.x に寄せる形で修正する予定となっております。
Vue CLI 2.x を使った導入方法
【2016/01/16 追記】
@nakajmg さんの 「vue-cli用のテンプレートを作成する」(http://qiita.com/nakajmg/items/806b50ddd474dd7ecb4f) を基にvue-cli対応を行いました。
現行バージョンでは、vue-cliで以下のように叩くことでコマンド一つで導入が可能となっております。
$ vue init potato4d/vue-dev-template
基本的な開発環境
アプリケーション基盤
Vue.js
今回は当然ながらVue.jsをメインで利用します。
Vue Router
ルーティング周りはVue Routerで処理します。
個人的には、特に沢山ページを生やさない場合でも、わかりやすさ重視で導入したいところではあります。
Vuex
状態管理は Vuex に寄せて管理します。
ビルドツール
基本的には webpack + babel の鉄板構成にて開発をしています。
babel-loader / vue-loader など基本的なローダーがほとんどですが、いくつか特徴的な構成があるためご紹介します。
また、 vue-loader は 15.x を利用しており、高速なビルドが可能となっています。
@
および ~
エイリアス
Vue.js のオフィシャルボイラープレートに使われているエイリアスである @
エイリアスと、 Nuxt.js にて使われているエイリアスである ~
をサポートしています。
どちらも src ディレクトリを参照します。
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 においては公式に提供されている開発セットを存分に利用しましょう。
開発ユーティリティ
HTTP: axios
HTTPリクエストには axios を利用します。
axios は、 ユニバーサルなライブラリであり、かつ API コールに適した「ベース URL の設定」や、「JSONの最適化」などの機能を備えています。
Vue.js から axios を利用する場合は、サンプルコードの src/plugins/axios.js のように Vue プラグインとして実装すると良いでしょう。
import axios from 'axios'
export default {
install(Vue) {
Vue.prototype.$axios = axios.create({
baseURL: process.env.API_ROOT
})
}
}
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のエコシステム内でも非常に多く使われているものとなります。
開発サーバー: webpack-serve
開発中のサーバーには webpack-serve を利用しています。
webpack-serve は、 webpack 4.0 以降の開発サーバーであり、メンテナンスモードに入った webpack-dev-server の後継となるツールです。
現状、 webpack 4.0 以降の開発におけるデファクトであり、 webpack の設定などをそのまま使い回すことができるため、積極的に利用すると良いでしょう。
プロジェクト構成
上記を全て盛り込んだ場合のサンプルの構成が以下となります。
次のセクションから、各項目を解説していきます。
.
├── LICENSE
├── README.md
├── build
│ └── webpack.config.js
├── package.json
├── src
│ ├── App.vue
│ ├── components
│ │ ├── AppGreeting.vue
│ │ └── TheHeader.vue
│ ├── index.html
│ ├── index.js
│ ├── pages
│ │ ├── _view.vue
│ │ ├── child.vue
│ │ └── index.vue
│ ├── plugins
│ │ └── axios.js
│ ├── routes
│ │ └── index.js
│ └── stores
│ └── index.js
├── static
└── yarn.lock
ルートのファイル/フォルダ群
.babelrc .eslintrc.js
諸々の設定の記述に使います。
public_html/
ここに実際公開するファイル群を配置していきます。
また、 webpack-serve の content の基点ディレクトリもここになります。
build/
webpack の設定ファイルなどを配置します。
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 の代わりに純粋な JavaScript によるストアパターンで実装しても良いでしょう。
utils/
ここには、複数のルーティング間で跨いで使いたい機能を記述します。
一般に公開されているnpmモジュールと同じ感覚で使いたいものは、全てここに記述すると良いでしょう。
運用する時に破綻させない仕組み
ざっと構成と基本的な役割だけを記述しましたが、これだけではわかりにくいポイントや破綻しやすいポイントの管理方法をまとめて紹介します。
コンポーネントの動作に合わせて特定の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パターンなどで固めるより、ある程度の柔軟性がある構成のほうが何かと開発が円滑となるのでこのような構成を取ることが多いですが、あまりサンプルを見かけなかったので、コンポーネント/ページ単位で開発したい人の助けになればと思います。
また「◯◯の場合はどうする?」と言ったご意見をコメントで頂ければ、頂いた分だけ内容が充実できますので、疑問点などがあれば質問をいただけますと幸いです。