vue-admin-templateを紐解く

2019年時点で、仕事ではサーバサイドのプログラムを書くことが多いのですが、ちょいとwebフロントエンドを管理画面風のUIで構築してみようと思い、良さげなテンプレートを探していたところ、[vue-admin-template](https://github.com/PanJiaChen/vue-element-admin)がシンプルで良さそうと思ったので中身を少し紐解いて見ました。（紐解くという程ではありませんが...）

で、記事本文自体は無駄に長いメモ書きに近いので、今回得られた知見を先に書いてみます。

## 今回得られた知見

- vue cliの基本的な使い方
  - vuc cliを使ったvueアプリケーションのディレクトリ構成
  - vue.config.jsによる基本的な設定
- Vuexの基本的な使い方
  - ゲッター
  - ミューテーション
  - アクション
  - モジュール
- Vue Routerの基本的な使い方
  - ルーティング
  - グローバルbeforeガード
- ES2015に関する知見
  - 引数分割束縛
  - スプレッド演算子
- mockjsの基本的な使い方
  - プロトタイプを作るときくらいしか用途を思いつきませんが...

感想としては、大きすぎないオープンソースを選んで、**具体的な動作を確認しながらソースコードや関連ドキュメントを読むと、効率的に理解が進む**なあと思った次第です。では、以下本文です。

## 環境
- vue 2.6.10
- vue-router 3.0.6
- vuex 3.1.0
- vue-cli-service 3.6.0


## 1. 開発モードで起動（`npm run dev`）すると何が起きるか

- `npm run dev`すると、`package.json`の scripts -> "dev": "vue-cli-service serve"が実行される
- vue-cli-serviceコマンドは`@vue/cli-service` によって提供されている
- `vue-cli-service serve` すると開発サーバが起動する
- vue-cli-serviceを使った開発サーバの実行時オプションの指定方法は以下の2通り
  - 設定ファイルに書く
  - コマンドライン引数として渡す
      - e.g. `port=7777 npm run dev`とすると、環境変数process.env.portに7777がセットされる
      - `process.env`とはnode.jsにおける環境変数のこと
      - `node`でnode.jsを起動して、`process.env`で確認できる
- vue-cliの設定ファイルは`vue.config.js`
- 基本的な設定、postcss、開発サーバ周り、linter、webpackなどの設定ができる

```javascript:vue.config.jsから一部抜粋
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
        
module.exports =
  publicPath: '/',
  outputDir: 'dist',
  assetsDir: 'static',
  lintOnSave: process.env.NODE_ENV === 'development',
  productionSourceMap: false,
  devServer: {
    port: port,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
      // change xxx-api/login => mock/login
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target: `http://127.0.0.1:${port}/mock`,
          changeOrigin: true,
          pathRewrite: {
            ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      }
    },
    after: require('./mock/mock-server.js')
  },
```


- `outputDir`で`npm run build`した時のファイル出力先ディレクトリを指定する
- `assetsDir`でcss、font、img、jsといったアセットファイルの出力先ディレクトリを指定する
  - 上記例だと、/dist/staticになる
- `lintOnSave`をtrueまたは'warning'にすると、開発中に出たlintエラーをwarningとして出力する
  - この設定は`eslint-loader`に依存している
  - デフォルトだとwarningはコンソールにだけ表示される
  - ブラウザにエラー表示するには`overlay`で制御できるっぽいが、今一挙動が把握できなかった
- `productionSourceMap`をfalseにすると、productionビルド時にsourcemapを生成しないので、ビルドが早くなる
- `devServer`で開発環境の設定ができる
  - `open`をtrueにするとnpm run dev した時に自動的にブラウザを開く
  - `proxy`はフロントエンドとバックエンド（API）を異なるホストで動かしている場合、開発用のバックエンドAPIを指定できる
      - e.g. RailsAPIをlocalhost:3000で動かしている場合 `devServer: { proxy: 'http://localhost:3000' }`

## 2. どのようにログイン処理をしているか

未ログイン状態で `http://localhost:9528` にアクセスした場合

### 2.1 どのようにログイン判定を行って、ログインフォームを表示しているか

- `permission.js`のrouter.beforeEach（グローバルbeforeガード機能）で、cookieからトークンを取得する
- cookieトークン取得は`utils/auth.js`で行っている（`js-cookie`ライブラリを使用）
- `to`は次にナビゲートされるルートオブジェクト。ルーティング設定ファイル（src/router/index.js）で`/`のredirectが/dashboardに設定されている
- `to.path`はホワイトリスト（/login）に含まれないので、リダイレクトパスに/dashboardが付いた状態で、ログインフォームを表示する

### 2.2 ログインフォームでどのようにログイン処理しているか

- `element-ui`のcustom-validationでユーザー名とパスワードのバリデーションを行っている
    - バリデーションメソッドを定義して、el-formの`:rules`に渡している
- Loginボタンを押すとvue.jsの`prevent`イベント修飾子でhandleLoginメソッドが実行される
- form要素にref="loginForm"と書いているので、`this.$refs.loginForm.validate`で再度バリデーションしている
- バリデーションOKなら、`this.$store.dispatch`を使ってVuexのuserモジュールのloginアクションを実行している
    - src/main.jsでVueオブジェクトを作る時に`store`オプションを渡すことで、各コンポーネントで`this.$store`として参照することができる
- loginアクションはimportしたapi/user.jsのloginを実行する
- api/user.jsのloginはutils/request.jsを使って、mock/user.jsに実装されているモックのAPIを呼び出す
- モックのAPIはユーザー名からtokenを取得して、レスポンスを返却する
- 返却されたレスポンスはutils/request.js内でaxiosの`interceptors.response`で返している（エラーコード毎にエラーメッセージを表示するのもここでやっている）
- Vuexのuserモジュールのloginアクションはレスポンスデータとして取得したtokenを処理する
    - SET_TOKENミューテーションをcommitする
    - utils/auth.jsのsetTokenでcookieにセットする

### 2.3 ログイン処理した後はどのようにリダイレクトしているか

- VuexのuserモジュールのloginアクションはPromiseを返す
- handleLoginは`this.$router.push`を使って次の画面に遷移する
- 遷移先のURLは、$routeをwatchしている（パラメータ変更の検知）ので、あらかじめルーティング設定のredirectパスである/dashboardがセットされている

## 3. どのようにログアウト処理をしているか

- `this.$store.dispatch`でVuexのuserモジュールのlogoutを呼び出す
- api/user.jsのlogoutを呼び出す
- モックのAPIの/user/logoutを呼び出す
- Vuexのuserモジュールのlogoutでログアウト処理をする
    - SET_TOKENミューテーションでtokenを消す
    - utils/auth.jsのremoveTokenでcookieのtokenを消す
- `this.$router.push`でログイン画面に遷移する
    - redirectクエリパラメータは、`this.$route.fullPath`によって元画面がセットされる

## 4. ExampleのTable画面

### 4.1 どのようにデータを取得しているか

- `created`フックでapi/table.jsのgetListを経由して、モックAPIからデータ取得する
- モックAPIでは`mockjs`ライブラリでデータを作っている

### 4.2 開発サーバを停止した状態のエラーハンドリング（通信エラー）はどのように行っているか

- utils/request.jsでaxiosの`interceptors.response`で通信エラーを拾っている
- 拾ったエラーメッセージはelement-uiのMessageコンポーネントで表示している


## 参考資料

- [HTML5テクニカルノート Vue CLI 3入門 06: CLIサービス](http://www.fumiononaka.com/Business/html5/FN1809001.html)
- [vue-cli 公式マニュアル](https://cli.vuejs.org/config/)
- [http-proxy-middleware 公式マニュアル](https://github.com/chimurai/http-proxy-middleware#proxycontext-config)
- [Vue Router グローバルbeforeガード](https://router.vuejs.org/ja/guide/advanced/navigation-guards.html)
- [Vue Router ルートオブジェクトプロパティ](https://router.vuejs.org/ja/api/#%E3%83%AB%E3%83%BC%E3%83%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)
- [Vue Router アクションのディスパッチ](https://vuex.vuejs.org/ja/guide/actions.html)
- [Vue Router パラメータ変更の検知](https://router.vuejs.org/ja/guide/essentials/dynamic-matching.html)
- [Vuex ガイド](https://vuex.vuejs.org/ja/guide/)
- [Element UI custom-validation-rules](https://element.eleme.io/#/en-US/component/form#custom-validation-rules)
- [Element UI Message](https://element.eleme.io/#/en-US/component/message#message)