Vue.js を使った中規模 Web アプリ向けのディレクトリ構造を考えた
最近 Vue.js を使って Web アプリを書いていて、どんなディレクトリ構造だと良いんだろうなーということを考えた結果を書きとめようと思います。Angular Best Practice for App Structureっぽい感じです。
ディレクトリ構造 #
├── app
│ ├── components
│ │ ├── component1
│ │ │ ├── component1.html
│ │ │ ├── component1.js
│ │ │ └── component1.css
│ │ ├── component2
│ │ │ ├── component2.html
│ │ │ ├── component2.js
│ │ │ └── component2.css
│ │ ├── component3
│ │ │ ...
│ │ ...
│ ├── filters
│ │ └── filter1.js
│ ├── directives
│ │ └── directive1.js
│ ├── plugins
│ │ └── plugin1.js
│ ├── images
│ │ ...
│ ├── index.html
│ ├── main.js
│ └── main.css
├── gulpfile.js
├── package.json
├── webpack.conf.js
...
コンポーネントごとにディレクトリを分け、コンポーネントを構成する html, css, js ファイルを同一のディレクトリ内に入れるのが良いと思います。html や css ファイルも一緒にすることで、関連するファイルを探すのが楽になります。コンポーネントを取り除くときや、リファクタリングする時も、一つにまとまってるので楽です。
vueify や vue-loader によってコンポーネントを一つの .vue ファイルで構成するというやり方もありますが、個人的には分割するほうが良いのではないかなーと思います。まず、.vue ファイル一つだとコードが長くなると見通すのがつらいです。vueify や vue-loader の例を見るとすっきりとして見やすいなーと思いますが、実際のコンポーネントはもっと量が多く、スクロールで行ったり来たりするのが結構つらいです。また、エディタが対応してないのがつらいというのもあります。html として扱えばシンタックスハイライトはしてくれますが、各種 Lint 系ツールが動かなかったり、動いても不具合があったりしてつらかったです。
app/ 直下の main.js には各種プラグインの読み込みや、設定、ルーティングなどを書いてます。main.js をエントリポイントにして、ここから各種コンポーネントなどを読み込んでいく形になります。また、main.css にはコンポーネントには入らない、リセット系のスタイルなどを定義するようにします。
また、filter や directive、plugin などは components と同じ階層にディレクトリを作って、それぞれ分けています。この辺りはまだ全然実装してないので、コンポーネントと合わせれば良いのではないかなーという程度の考えです。
具体的にどう実装するのか #
ディレクトリ構造を決めた後は、実際にその構造で動くように実装をしました。具体的には、webpack を用いてコンポーネント間の依存関係を解決し、各コンポーネントの css, js も読みこむようにしました。ここでは、webpack の具体的な設定を説明するのではなく、最終的にどのように書けるようにしたかを書きます。
// component1.js の例
require('./component1.css');
module.exports = {
template: require('./component1.html'),
...
components: {
component2: require('../component2/component2')
},
filters: {
filter1: require('../../filters/filter1')
},
...
};
それぞれの js ファイルはコンポーネントのオプションオブジェクトを export するように書きます。Vue.component() メソッドは使用しません。filter や directive なども同様です。export させることで、コンポーネント間の依存関係を webpack に解決させてます。また、そういった書き方をしているので、各コンポーネントのオプションオブジェクトの中で、依存するコンポーネントなどを指定する必要があります。コンポーネントごとに依存関係を指定するのは面倒ですが、ソースコード内に依存関係が明示されるのは良いのではないかと考えています。
html, css ファイルは、そのコンポーネントの js ファイルの中で読み込ませます。webpack の html-loader を使えば html を文字列として取得し、template に渡すことができます。また、style-loader を使えば、読み込んだ css を style 要素としてページに追加することができます。html や css は普通に書けば良いです。
もうちょっと何とかしたい点 #
以下の点をもうちょっと何とかしたいです。
- あるコンポーネントから別のコンポーネントや、フィルター、ディレクティブを参照する時の表現が冗長
- CSS Module に対応したい
- いろんな部分で使い回すようなモジュールはどういう扱いにするのか?
例えば上の例だと、component2 を参照する時は component2 を二回書かないとダメですし、filter1 に関しては ../../
がつらい感じです。前者についてはファイル名をすべて index.(ext) にすると、一回の記述ですみます。しかし、すべて index.(ext) にしてしまうと、エディタで開いた時がつらいです。後者については、webpack の resolve.root の設定に app/ ディレクトリを入れると省略はできますが、ライブラリ系以外で resolve.root 使うのはどうなんだろうなーという感じです。
また、css に関して、コンポーネントごとに分けてはいますが、普通に衝突します。css-loader は CSS Module にも対応しているようなので、試してみたいです。
いろんな部分で使い回すようなモジュールに関しては、とりあえず Vue.js のプラグインとして書いてみていますが、普通に Vue.js とは独立した書き方で書いて、それを import させても良さそうな気がします。
最後に #
Web ページのディレクトリ構造といえばファイルの種類ごとに分けるのが一般的だったと思うのですが、各種フレームワークとか webpack とかを使ってコンポーネント化を意識すると、ファイルの種類ごとに分けると依存関係がわからなくなってつらくなることが多いように感じます。初めは少し抵抗を覚えますが、コンポーネントごとに分けるのも良いのではないかと考えます。
今回のディレクトリ構造を中規模向けとしたのは、おそらく大規模になってくるとコンポーネントの数が多すぎて、うまく目的のものを見つけられないということが起こると考えたからです。大規模な Web アプリの場合は、ページごとにさらにディレクトリを分けるなどする必要があるんじゃないかなと思います。