Vue2のディレクトリ構成を考える

# はじめに
Vue2が良さそうなので、実装しながらディレクトリ構成について考えてみたいと思います。
Vuexも使います。

# 環境構築
Vue.jsではvue-cliが用意されており、最初の開発環境が簡単に作れます。
手っ取り早く動く環境を作りたいときなど試してみると良いです。

```
npm install -g vue-cli
vue init webpack my-project
cd my-project
npm install
npm run dev
```

またvue-cliは[ Webpack / Browserify ]の両方に対応されており、以下の4つのテンプレートが用意されています。

- browserify
- browserify-simple
- webpack
- webpack-simple

vue-cliはWebpackの2系に移行済みです！

# ディレクトリ構成
こちらが現在のディレクトリ構成になります。
規模が大きくなってきても耐えれる構成にしたいということを意識して考えました。

```
├── assets
├── dist
├── gulp
├── gulpfile.js
├── package.json
├── src
│   ├── App.vue
│   ├── components
│   │   └── Sample
│   │       ├── Sample.vue
│   │       ├── style.css
│   │       └── template.html
│   │   └── globals
│   │       └── Header
│   │           ├── Header.vue
│   │           ├── header.html
│   │           └── header.scss
│   ├── constants
│   │   └── constant.js
│   ├── index.html
│   ├── index.js
│   ├── pages
│   │   └── RouteSample
│   │       ├── RouteSample.vue
│   │       ├── style.css
│   │       └── template.html
│   ├── routes.js
│   ├── utils
│   └── vuex
│       ├── actions
│       │   └── sample.js
│       ├── getters
│       │   └── sample.js
│       ├── modules
│       │   └── sample.js
│       ├── store.js
│       └── types.js
├── test
│   └── unit
│       ├── coverage
│       ├── karma.conf.js
│       └── specs
│           ├── components
│           │   └── Sample.spec.js
│           └── vuex
│               ├── actions
│               │   └── sample.spec.js
│               ├── getters
│               │   └── sample.spec.js
│               └── modules
│                   └── sample.spec.js
└── yarn.lock
```

### assets
静的ファイル

### dist
公開ディレクトリ

### gulp
Gulpタスク及び、Webpack関連

### App.vue
vue-routerのrouter-viewを記載します。
ルーティング外のコンポーネントもこちらに記載します。

```html:App.vue
<template>
  <div id="app">
    <header-view></header-view>
    <router-view></router-view>
  </div>
</template>
<script>
import HeaderView from './components/globals/Header/Header'

export default {
  components: {
    HeaderView
  }
}
</script>
```

### components
こちらには親コンポーネントからprops経由で値を受け取り、表示するコンポーネントを配置します。
直接Vuexにアクセスしないように作る想定です。
通常はHTML, JS, CSSを1ファイルでvueファイルとして記述しますが、規模が大きい画面だと
vueファイルが膨大になるのを避けたかったので、それぞれファイルを分けてsrcでインポートしています。
つまり以下のような書き方です。

```html:Sample.vue
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
```
このようにすることで、HTML, JS, CSSを分割して書くことができます。

※scriptを分けるとWebpackの```eslint-loader```が効いてくれず、現状はvueファイル内にscriptを書いてます。

```html:Sample.vue
<template src="./template.html"></template>
<style src="./style.css"></style>
<script>
export default {
  props: {
    word: {
      type: String,
      required: true
    }
  }
}
</script>
```

### globals
こちらにはルーティング外のコンポーネントを置きます。
ヘッダーやフッターなどが該当します。

### constants
定数を定義

### index.html
公開HTML

### index.js
エントリポイントとなるJSです。
ここでやっていることは
・vue-routerを初期化する
・vuexのstoreをセットする
です。

```js:index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
import store from './vuex/store'
import App from './App'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes
})

/* eslint-disable no-new */
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
/* eslint-enable no-new */
```

### pages
ルーティングに対応するコンポーネントをここに置くようにします。
ここにあるコンポーネントが親コンポーネントとなり、getters経由でstateを取得し、
components配下の子コンポーネントにprops経由でstateを渡すようにしています。

```html:RouteSample.vue
<template src="./template.html"></template>
<style src="./style.css"></style>
<script>
import { mapActions, mapGetters } from 'vuex'
import { CLICK } from '../../vuex/types'
import Sample from '../../components/Sample/Sample'

export default {
  methods: {
    ...mapActions({
      click: CLICK
    })
  },
  computed: {
    ...mapGetters([
      'word'
    ])
  },
  components: {
    Sample
  }
}
</script>
```

```html:template.html
<sample
  :click="click"
  :word="word">
</sample>
```

### routes.js
ルーティングが増えたらこちらに記載します。

```js:route.js
const RouteSample = require('./pages/RouteSample/RouteSample')

export default [
  {
    path: '/sample',
    component: RouteSample
  }
]
```

### utils
ユーティリティー

### actions
機能ごとにアクションを分けています。
公式ではアクションは1ファイルに書くことになっていますが、
規模が大きくなると1ファイルに書くのは厳しいと判断しました。

```js:sample.js
import { CLICK, OTHER_CLICK } from '../types'

export default {
  [CLICK] ({ dispatch, commit }, param) {
    commit(CLICK, param) // mutationsを呼ぶ
    dispatch(OTHER_CLICK, param) // 他のアクションを呼ぶ
  }
}
```

### getters
アクションに合わせてこちらも機能ごとに分けています。
ただし、gettersに定義する変数は全体で重複があってはならない為、注意が必要です。
つまり以下のような書き方です。

```js:sample.js
export default {
  word: state => {
    return state.word
  }
}
```
```js:sample2.js
export default {
  word: state => {
    return state.keyword
  }
}
```
これをビルドして動作させると、以下のようなエラーが出ます。
```[vuex] duplicate getter key: word```

※gettersを1ファイルにしてしまうのもありだと思います。

### modules
Vuexのactions, getters, state, mutationsをまとめてexportしています。
gettersと違いstateは機能ごとに持てる為、機能内で重複しなければ大丈夫です。

```js:sample.js
export default {
  state: {
    word: 'before'
  },
  mutations: {
    [CLICK] (state, payload) {
      state.word = payload.word
    }
  }
}
```
```js:sample2.js
export default {
  state: {
    word: false
  },
  mutations: {
    [OTHER_CLICK] (state, payload) {
      state.word = payload.word
    }
  }
}
```

上記のような書き方をしても、以下のようにstateを持ってくれます。

```
state {
  sample {
    word: 'before'
  },
  sample2 {
    word: false
  }
}
```

### store.js
modulesを読み込みstoreを作ります。
ここでstoreを作る際に```strict: true```を設定しています。（厳密モード）
これを設定しておくと、stateの予期せぬ変更（ミューテーションハンドラの外部で変更）に対して、エラーを投げてくれます。
ただし、ステージングや本番環境ではパフォーマンス劣化の原因となるので、falseを設定してください。

```js:store.js
import Vue from 'vue'
import Vuex from 'vuex'
import sample from './modules/sample'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'staging'

export default new Vuex.Store({
  modules: {
    sample
  },
  strict: debug
})
```

### types.js
mutation-typeを定義しています。全体で一意の値を設定します。

### test
上述したvue-cliの中にも単体テストとE2Eテストが組み込まれているので、参考になります。
vue-cliは以下で構築されています。
・```karma + mocha + chai + phantomJS```
・カバレッジ計測に```istanbul```
・スタブJSに```sinon```

その他のライブラリを検討するならば、
・```Jasmine```（もしくは```Jest```）
・```power-assert```
辺りも検証してもいいのではないでしょうか。

# ESLint
Vue.jsのLintは```eslint-plugin-vue```,```eslint-config-vue```を使いました。
一旦、これでやってみようと思ってます。

```js:.eslintrc
"root": true,
"parser": "babel-eslint",
"parserOptions": {
  "sourceType": "module"
},
"extends": "vue",
"plugins": [
  "html"
]
```

# さいごに
Vue.jsを少し書いてみて、全体的にシンプルでライブラリも使いやすくて良いと感じました！
後、日本語ドキュメントも豊富です。
vue-devtoolsもいい感じでした。