Nuxt v2.9.2でTypeScript, eslint, Prettier環境構築 + VSCodeの設定(20190921tailwind.css導入追記)

> 20190921 tailwind.css導入追記
> 20190921 plugin:nuxt/recommendedについて追記

##はじめに
今までNuxtは使ってきたものNuxt+TypeScriptは導入したことがなかったので導入してみました。
意外と環境構築で手間取ったので、備忘録として残しておきたいと思います。

##想定
* Nuxtは使っていたけど、TypeScriptは入れてなかった人
* Nuxt以外でTypaScriptは使っていて、TypeScript自体は書いている人

##開発環境
* Nuxt 2.9.2
* node 12.6.0
* TypeScript 3.5.3

## Nuxtのインストール
まずは公式に沿ってNuxtをインストールをしていきます。
[公式インストール手順](https://ja.nuxtjs.org/guide/installation)

```bash
yarn create nuxt-app test
```

### プロジェクトの作成

いくつか質問されますが今回は以下のような形でプロジェクトを作成しました。

* Project name: test
* Project description ]: My super-duper Nuxt.js project
* Author name: test
* Choose the package manager : yarn
* Choose UI framework : None
* Choose custom server framework : None
* Choose Nuxt.js modules : 選択しない
* Choose linting tools : Prettier
* Choose test framework : None
* Choose rendering mode : Universal (SSR)
* Choose development tools : 選択しない

## 最低限の環境で動作を確認

ここまででとりあえず最低限の動く状態が作成されたはずです。
まずはこの状態で動くことを確認します。

```
cd test
yarn dev
```

上記をターミナルで叩くと他にローカルで立ち上げているアプリケーションがなければ、以下のアドレスで動作しているはずです。

[localhost:3000](localhost:3000)
![localhost_3000_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191638/cdeaf626-b0a1-832c-4d7c-ef74f8f64c72.png)

## TypeScriptの導入

この環境にTypeScriptを入れていきます。

ちなみにNuxtに関してはTypeScriptの導入方法がバージョンが上がるたびに変わっていて
ネットの記事を見ても、最新のものと昔のものでインストール方法が異なるようです。

現時点(2019年9月)でNuxt公式からNuxtでのTypeScriptの運用に関して公式のドキュメントを出しているのでそれを参考にインストールしていきます。

[Nuxt TypeScript](https://typescript.nuxtjs.org/)

### インストール

公式のインストールページを参考にTypeScriptを入れていきます。

[Setup](https://typescript.nuxtjs.org/guide/setup.html)

```
yarn add --dev @nuxt/typescript-build
```

上記をインストール後nuxt.config.jsに以下を追記します。

```js:nuxt.config.js
export default {
  buildModules: [
    '@nuxt/typescript-build' // ←追記
  ]
}
```

tsconfig.jsonを作成します。

```
touch tsconfig.json
```

tsconfig.jsonの中身は公式に記載がある内容をそのまま記載します。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}
```

## index.vueをTypeScriptに書き換え
 
ここまで入れたらindex.vueをTypeScriptに変えて動作するか確認します。

他のPJでクラスベースでの書き方をしていたため、今回もクラスベースで記載していきます。
まずはvue-class-componentを使用するためにvue-property-decoratorをインストールします。

```
yarn add vue-property-decorator 
```

変更前

```vue:index.vue
<template>
  <section class="container">
    <div>
      <logo />
      <h1 class="title">test</h1>
      <h2 class="subtitle">My awesome Nuxt.js project</h2>
      <div class="links">
        <a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation</a>
        <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey">GitHub</a>
      </div>
    </div>
  </section>
</template>

<script>
import Logo from '~/components/Logo.vue'

export default {
  components: {
    Logo
  }
}
</script>

```

↓↓↓↓↓　変更　↓↓↓↓↓

```vue:index.vue
<template>
  <section class="container">
    <div>
      <logo />
      <h1 class="title">test</h1>
      <h2 class="subtitle">My awesome Nuxt.js project</h2>
      <div class="links">
        <a href="https://nuxtjs.org/" target="_blank" class="button--green"
          >Documentation</a
        >
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
          >GitHub</a
        >
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component({
  components: {
    Logo: () => import("~/components/Logo.vue")
  }
})
export default class Index extends Vue {}
</script>
```

ここまで動作を確認します。

[localhost:3000](localhost:3000)
無事動作していれば導入までは完了です。

## eslintの導入


TypeScriptを書く際にもやはりlinter入れておきたいですよね。
ということでeslintを入れていきます。
これも[NuxtTypeScriptの公式](https://typescript.nuxtjs.org/guide/lint.html)に記載があります。

> All you need is to install `@nuxtjs/eslint-config-typescript:`

とのことなので、これを入れていきます。

```
yarn add -D @nuxtjs/eslint-config-typescript
```

※20191019 以下ご指摘いただき修正いたしました！
### .eslintrc.jsの作成

eslintの設定ファイル(.eslintrc.js)を作成します。

```bash
touch .eslintrc.js
```

下記を`.eslintrc.js`に記載します。

```js:.eslintrc.js
module.exports = {
  extends: [
    '@nuxtjs/eslint-config-typescript'
  ]
}
```
package.jsonのscriptに以下を追加します。

```json:package.json
"lint": "eslint --ext .ts,.js,.vue ."
```

ここでlintが通るか試して見たいと思います。

```
yarn lint
```

もし`eslint: command not found`のエラーが出た場合は、
`yarn add eslint`でインストールしてから試して見てください。

```
$ eslint --ext .ts,.js,.vue .
✨  Done in 1.75s.
```
コンソールに上記のように出れば問題なく動作しています。

## linter with Webpack

この時点だと単にeslintをコマンド経由で叩かないといけないので、
保存時に都度チェックがかかるようにしていきます。

`nuxt.config.js`のbuildに以下を追加します。

```js:nuxt.config.js
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  },
```

ここで以下のようなエラーが発生することがあります。

```
ERROR in multi eventsource-polyfill webpack-hot-middleware/client?reload=true&timeout=30000&ansiColors=&overlayStyles=&name=client&path=/__webpack_hmr/client ./.nuxt/client.js
Module not found: Error: Can't resolve 'eslint-loader' in '/Users/nakama/Documents/workspace/private/test'
```
これはeslint-loaderをインストールしていなかったためにおきている状態なので、eslint-loaderとeslintそのものをinstallします。

```
yarn add eslint-loader eslint --save-dev
```

### .eslintrcの変更

eslintの設定ファイル(.eslintrc)を変更します。

設定を作成する前に先ほどextendsに追加した、`@nuxtjs/eslint-config-typescript`の中身を確認しておきます。

```js:@nuxtjs/eslint-config-typescript/index.js

module.exports = {
  extends: [
    '@nuxtjs'
  ],
  plugins: ['@typescript-eslint'],
  parserOptions: {
    parser: '@typescript-eslint/parser'
  },
  rules: {
    '@typescript-eslint/no-unused-vars': ['error', { args: 'all', argsIgnorePattern: '^_' }]
  }
}
```

中を見て見ると、予想以上に中身は少ない印象です。
今まで`parserOptions`や`plugins`に入れていたものが入っています。
この辺りに記載があるものに関しては、あえて`.eslintrc`で再度記載する必要はなさそうです。

上記の内容とNuxtの公式を参考にして最終的に以下の通りとしました。

[Nuxt-開発ツール](https://ja.nuxtjs.org/guide/development-tools/#eslint-%E3%81%A8-prettier)

```js:.eslintrc.js
module.exports = {
    root: true,
    env: {
        browser: true,
        node: true
    },
    extends: [
        '@nuxtjs/eslint-config-typescript',
        'plugin:prettier/recommended',
        'prettier',
        'prettier/vue'
    ],
    plugins: [
        'vue'
    ],
    rules: {
        'vue/html-closing-bracket-newline': 'off',
        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'space-before-function-paren': 0
    }
};
```

ちなみに'plugin:nuxt/recommended'を入れるとエラーで止まるので、外しています。

```
Module build failed (from ./node_modules/eslint-loader/dist/cjs.js):
TypeError: Cannot read property 'forEach' of undefined
    at Linter.parseResults (/Users/nakama/Documents/workspace/private/test3/node_modules/eslint-loader/dist/Linter.js:121:13)
    at Linter.printOutput (/Users/nakama/Documents/workspace/private/test3/node_modules/eslint-loader/dist/Linter.js:85:26)
    at Object.loader (/Users/nakama/Documents/workspace/private/test3/node_modules/eslint-loader/dist/index.js:26:10)
```

解決次第追記します。

> 20190921追記
上記に関しては単に`eslint-plugin-nuxt`が入っていない事に起因していました。
ですので、以下のコマンドで該当のプラグインを入れればエラーは解消します。

```bash
yarn add eslint-plugin-nuxt --save-dev
```

### 変更時にlintが走るかどうか確認

ここでindex.vueを一部変更して、lintが走るか確認して見ます。

```vue:index.vue
<template>
  <section class="container">
    <div>
      <logo />
      <h1 class="title">test</h1>
      <h2 class="subtitle">My awesome Nuxt.js project</h2>
      <div class="links">
        <a href="https://nuxtjs.org/" target="_blank" class="button--green"
          >Documentation</a
        >
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
          >GitHub</a
        >
      </div>
    </div>
  </section>
</template>

```

↓↓↓↓↓変更↓↓↓↓↓

```vue:index.vue
<template>
  <section class="container">
    <div>
      <logo />
      <h1 class="title">test</h1>
      <h2 class="subtitle">My awesome Nuxt.js project</h2>
      <div class="links">
        <a href="https://nuxtjs.org/" target="_blank" class="button--green"
          >Documentation</a
        >


        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
          >GitHub</a
        >
      </div>
    </div>
  </section>
</template>
```

以下のようなエラーがコンソール上に出れば問題なく動作しています。

```bash
/test/pages/index.vue
  12:9  error  Delete `⏎········`  prettier/prettier

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.
```

## linter + Prettier with VSCode

個人的にTypeScript書くならVSCodeで書くのが一番楽だと感じています。
そこでVSCodeでlinterとPrettierによる自動整形まで行いたいと思います。

ちなみにPrettierに限らずフォーマットをするために`editor.formatOnSave`を`true`にしている人もいるかと思いますが、今回の構成だとVSCodeデフォのフォーマッタが邪魔をすることがあるので`editor.formatOnSave`はfalseに設定しておきます。
もちろんこのプロジェクトのワークスペース内だけで構いません。

### VSCodeのプラグインの導入

あらかじめプラグインとしてeslintとveturを入れておきます。
インストールの方法や詳細は割愛します。

### settings.jsonの設定
VSCcodeのsettings.jsonに以下の設定を追加します。

```json:settings.json
{
  "eslint.validate": [
    {
      "language": "vue",
      "autoFix": true
    },
    {
      "language": "javascript",
      "autoFix": true
    },
    {
      "language": "javascriptreact",
      "autoFix": true
    }
  ],
  "eslint.autoFixOnSave": true,
  "editor.formatOnSave": false,
  "vetur.validation.template": false
}
```

先ほどと同じようにして、index.vueを変更してみて今度は自動整形されて、エラーが出なければ正常に動作しています。

## tailwind.cssの導入(20190921追記)

tailwindのmodule版のインストールを行います。

1.インストール

```bash
yarn add @nuxtjs/tailwindcss --save-dev
```
2.nuxt.config.jsに追記

```js:nuxt.config.js
devModules: [
  '@nuxtjs/tailwindcss'
]
```

3.`yarn dev`を実行して、`tailwind.config.js`と`assets/css/tailwind.css`が生成されることを確認

4.index.vueにボタンを追加

```vue
<button
  class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
>
  tailwind Button
</button>
```

![localhost_3000_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191638/099f566d-aa2e-6594-2643-68a7e41a581f.png)




## (おまけ)Veturのバグか?うまく変換されないことがある。

なぜかうまく変換されない事象が発生することがある、、、。

後述するISSUEから抜粋するとこんな感じ

これが

```html
    <li>
      <a href="https://vuejs.org"
target="_blank">Core Docs</a>
    </li>
```

こうなって

```html
    <li>
      <a href="https://vuejs.org" target="_blank">Core Docs</a>
    </li>
```

こうなる

```html
    <li>
      <a href="https://vuejs.org"
target="_blank">Core Docs</a>
    </li>
```

初めどこが原因かよくわからなかったんですが、veturの公式に似たようなISSUEが上がってました。

[The right way to setup vscode vetur eslint prettier](https://github.com/vuejs/vetur/issues/1094)

最後のレスを見ると以下のような言及が。

---

>> But then how do you do 'auto fix on save' if you turn it off.
>> しかし、それをオフにした場合、「保存時に自動修正」を行うにはどうすればよいですか。

> Feel free to turn it on for wrong autofixes, which I'm not responsible for.
> 間違った自動修正については、お気軽に有効にしてください。私は責任を負いません。

ちなみにこのやり取りの前に以下のようなやりとりがあった。

> `eslint.autoFixOnSave`をオフにすると、フォーマットの問題はなくなります。これらの「自動修正」のソースはeslint-plugin-vueに由来し、ほとんどの場合間違っています。

特定のパターンでしか発生しないので一旦放置しています。
解決策をご存知の方がいましたらぜひご教授できますと幸いです。

## 最後に

最後まで読んでくださりありがとうございました。

フロントエンドだとプロダクト作成前に環境を整えるのが一番つらいのはよくある気もしますが、今回は久々にハマりました。
何しろNuxtのTypeScript環境に関しては、バージョンが変わるたびにインストール方法が変わるのこと、eslintとprettierの登場人物が多すぎることに起因している気がします。
ネット上の記事に関しても、公式でもバージョンや作成日時によって内容がガラッと変わっていたりします。

本来もう少し楽なやり方もあるかもしれませんが、とりあえず現段階ではこれで進めていきたいと思います。



## 参考
* https://typescript.nuxtjs.org/
* https://ja.nuxtjs.org/guide/development-tools/#eslint-%E3%81%A8-prettier
* https://qiita.com/hiropy0123/items/4ee85fdb9b19bc89056
* https://qiita.com/shosho/items/742723142980092b61eff
* https://github.com/vuejs/vetur/issues/1094