laravel+vueのプロジェクトを一本化

#概要
サーバ側:laravel フロント側:vue
の構成で開発した社内プロダクトはそれぞれ別プロジェクトでリポジトリとして立ち上げた。
その後、とある仕組みで一本化できるとわかったのでリファクタした。

#基本的な知識
laravelの中にvueを入れるイメージ。laravel-mixというwebpackのラッパーライブラリ(名前にlaravelとついているがあまりlaravelとは関係ないみたい。。)でvueをビルドして組み込む。

##ステップバイステップ
laravelのプロジェクトの"resources/js"以下にvueプロジェクトのsrcフォルダ以下を持っていく。
"resources/js"直下の"app.js"がエントリーポイントになるが、今回のプロジェクトではtypescriptを用いているため、main.tsに置き換えた。

###webpack.min.js
laravel-mixの動きを定義するファイル。

```config:[webpack.mix.js]
const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

// mix.js('resources/js/app.js', 'public/js')
//     .sass('resources/sass/app.scss', 'public/css');

mix.browserSync({
    proxy: 'localhost:8000',
    files: [
        './**/*.css',
        './app/**/*',
        './config/**/*',
        './resources/views/**/*.blade.php',
        './resources/views/*.blade.php',
        './routes/**/*'
    ],
    watchOptions: {
        usePolling: true,
        interval: 100
    },
    open: "external",
    reloadOnRestart: true
}).ts('resources/js/main.ts', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');
```

typescriptを使っているのでtsconfig.jsonをvueプロジェクトからlaravelプロジェクト直下にコピーする。パスを移動先に合わせて見直す。

```config:[tsconfig.json]
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "resources/js/*"  <-★
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "resources/**/*.ts",  <-★
    "resources/**/*.tsx", <-★
    "resources/**/*.vue"  <-★
  ],
  "exclude": [
    "node_modules"
  ]
}
```
"webpack-env"の定義が見つかりません。というエラーが出たので、resources/js直下に以下２つのファイルを配置して回避した。

```[shims-tsx.d.ts]
import Vue, { VNode } from "vue";

declare global {
    namespace JSX {
        // tslint:disable no-empty-interface
        interface Element extends VNode {}
        // tslint:disable no-empty-interface
        interface ElementClass extends Vue {}
        interface IntrinsicElements {
            [elem: string]: any;
        }
    }
}
```

```[shims-vue.d.ts]
declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}
```


vue側で使っているnodeモジュールをlaravelのpackage.jsonにマージしてインストールする。

###webpackのエイリアス
importやrequire宣言で短縮パスを使うためのエイリアス宣言は以下で通った。
"laravel-mix-alias"をインストールし、webpack.min.jsで定義。

```[webpack.min.js]
const mix = require('laravel-mix');
require('laravel-mix-alias');

mix
  .alias('@', '/resources/js')
  .browserSync({
    proxy: 'localhost:8000',
    // files: [
    //     './**/*.css',
    //     './app/**/*',
    //     './config/**/*',
    //     './resources/views/**/*.blade.php',
    //     './resources/views/*.blade.php',
    //     './routes/**/*'
    // ],
    // watchOptions: {
    //     usePolling: true,
    //     interval: 100
    // },
    open: "external",
    reloadOnRestart: false,
    
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: "ts-loader",
                options: { appendTsSuffixTo: [/\.vue$/] },
                exclude: /node_modules/
            }
        ]
    }
}).ts('resources/js/main.ts', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');
```

###babelエラー
"Support for the experimental syntax 'classProperties' isn't currently enabled"のエラーが発生した。これに対してはbabel.config.jsを以下の内容でを配置して回避した。

```[babel.config.js]
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  plugins: ["@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-class-properties"]
};
```

###npm run watchでエンドレスビルドループになってしまう。
webpack.min.jsでビルド対象としているファイルに、下記のようなcss読み込みコードがあるとダメみたい。

```
import "element-ui/lib/theme-chalk/index.css";
import "../../public/element-ui-custom.css";
```
なのでこれはindex.htmlやApp.vueなどのルートテンプレートに持っていく。

##hot reload時に毎回認証が必要になるのを防ぐ
browserSyncでソース変更時に自動でブラウザがリロードされるが、vueで認証メカニズムを入れていると再認証が毎回必要になり、せっかくのhot reloadの恩恵が得られない。
vuex-persistedstateでリロード時も認証保持されるようにして本当の意味でのhot reloadに対応させる。
ライブラリインストール

```sh
$ npm install --save vuex-persistedstate
```
vuexのstoreに保持する宣言を追加する。

```diff:[web/store/index.ts]
import Vue from "vue";
import Vuex from "vuex";
import createPersistedState from "vuex-persistedstate";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    _isDirty: false, // 編集有りか否か
    _authenticate: null, // 認証情報
    _isMenuShow: true
  },
  getters: {
    isDirty: function(state) {
      return state._isDirty;
    },
    authenticate: function(state) {
      return state._authenticate;
    },
    isMenuShow: function(state) {
      return state._isMenuShow;
    }
  },
  mutations: {
    isDirty(state, payload: boolean) {
      state._isDirty = payload;
    },
    authenticate(state, payload: any) {
      state._authenticate = payload;
    },
    isMenuShow(state, payload: boolean) {
      state._isMenuShow = payload;
    }
  },
  actions: {},
  modules: {},
+  plugins: [
+    createPersistedState({
+      key: "csf",
+      storage: window.sessionStorage
+    })
+  ]
});
```
これでリロード時も認証されたままになる。
通常のstore情報をクリアしての処理のままでログアウトもきちんと動作する。
createPersistedStateのパラメータ解説

#####key 他のアプリや別タブと区別するための識別子。つけないと別タブの認証状況をひきずってしまう。
#####storage 保持先。指定と挙動は以下。
|     指定    | 挙動 |
| ---- | ---- |
|  指定なし |  localstorageとなりブラウザを閉じてもデータを保持する。  |
|  window.sessionStorage| ブラウザのセッションに保持する。 |
|  ゲッターやセッターのメソッド | クッキーに保持する。 |