Laravel + Vue + TypeScript + Eslint

この記事ではLaravelのフロントエンドをVue+TypeScriptで実装します。
TypeScriptの整形にはESlintを使用します。

使うもの
- Laravel Laravel_Mix
- TypeScript ESlint
- Vue Vue-Router

使わないもの
- TSlint Pretter

# 環境
- Laravel v7.5
- Node v13
- Vue v2.6

# 準備
Laravelのプロジェクトを作成
```bash
composer create-project --prefer-dist laravel/laravel app
```

Vueをインストールする
```bash
npm i
npm i -D vue vue-router vue-template-compiler
npm i -D typescript ts-loader
npm i -D eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser
```

# 追加・修正するファイル一覧
```
├---resources
|   ├---ts(追加)
|   |   ├---App.vue
|   |   ├---app.ts
|   |   ├---pages
|   |   |   └---index.vue
|   |   ├---route.ts
|   |   └---vue-shim.d.ts
|   └---views
|       └---index.blade.php(追加)
├---routes
|   └---web.php(修正)
├---.eslintrc.json(追加)
├---tsconfig.json(追加)
└---webpack.mix.js(修正)
```

# ルートとテンプレートを追加修正
routes/web.php
```php
Route::get('/{any?}', function () {
    return view('index');
})->where('any', '.+');
```

resources/views/index.blade.php
```html
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ config('app.name') }}</title>

  <!-- Scripts -->
  <script src="{{ mix('js/app.js') }}" defer></script>

  <!-- Fonts -->

  <!-- Styles -->

</head>
<body>
  <div id="app"></div>
</body>
</html>
```

# Laravel Mixを修正
webpack.mix.js
```js
const mix = require('laravel-mix')

mix.js('resources/ts/app.ts', 'public/js')
  .sass('resources/sass/app.scss', 'public/css')
  .version().sourceMaps()

mix.webpackConfig({
  resolve: {
    extensions: ['.vue', '.ts']
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: { appendTsSuffixTo: [/\.vue$/] },
        exclude: /node_modules/
      }
    ]
  }
})
```

# TypeScriptのソースを追加
tsconfig.json
```json
{
  "compilerOptions": {
    "target": "es5",
    "strict": true,
    "module": "es2015",
    "moduleResolution": "node",
  },
  "include": [
    "./resources/ts/**/*"
  ]
}
```

resources/ts/app.ts
```ts
import Vue from 'vue'
import router from './router'
import App from './App.vue'

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App />'
})
```

resources/ts/App.vue
```html
<template>
  <div>
    <main>
      <div>
        <RouterView />
      </div>
    </main>
  </div>
</template>
```

resources/ts/pages/index.vue
```html
<template>
  <h1>Index</h1>
</template>
```

resources/ts/router.ts
```ts
import Vue from 'vue'
import VueRouter from 'vue-router'

import Index from './pages/index.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: Index
  }
]

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

export default router
```

resources/ts/vue-shim.d.ts
```ts
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
```

.eslintrc.json
```json
{
  "extends": [
    "eslint:recommended",
    "plugin:vue/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "parser": "vue-eslint-parser",
  "parserOptions": {
    "parser": "@typescript-eslint/parser",
    "sourceType": "module"
  },
  "rules": {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [
      2,
      {
        "multiline": "never"
      }
    ],
    // 複数属性を1行に記述
    "vue/max-attributes-per-line": "off",
    // 不要なカッコは消す
    "no-extra-parens": "warn",
    // 無駄なスペースは削除
    "no-multi-spaces": "error",
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [
      "error",
      {
        "max": 1
      }
    ],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [
      "error",
      "never"
    ],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": "error",
    // セミコロンは禁止
    "semi": [
      "error",
      "never"
    ],
    // 文字列はシングルクオートのみ
    "quotes": [
      "error",
      "single"
    ],
    // varは禁止
    "no-var": "error",
    // jsのインデントは２
    "indent": [
      "error",
      2
    ],
    // かっこの中はスペースなし！違和感
    "space-in-parens": [
      "error",
      "never"
    ],
    // コンソールは許可
    "no-console": "off",
    // カンマの前後にスペース入れる？
    "comma-spacing": "error",
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": "error",
    // キー
    "key-spacing": "error",
    // キーワードの前後には適切なスペースを
    "keyword-spacing": "error"
  }
}
```

これで下のコマンドでビルドできる
```bash
npm run dev
npm run watch
npm run prod
```
※もしcross-envでエラーが出たらグローバルインストールする
```bash
npm i -g cross-env
```
後はIDE毎にESLintでTypeScriptの整形を行うよう設定する。
ちなみにVS-Codeであれば


# 参考
https://www.hypertextcandy.com/vue-laravel-tutorial-introduction
https://megu-tech.hatenablog.com/entry/2019/08/02/154710
https://qiita.com/Naturalclar/items/21ca036339f8ef48e777