Nuxt.js で TypeScript を使って一通り試したら色々ハマった件

# はじめに

これは Nuxt.js で TypeScript を使って一通り試そうとしたら、
色々なところでつまずいてしまったのでメモに残すお話です。

また、この記事は [Nuxt.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nuxt-js) 21日目の記事です。

今までに Vue.js の経験はありますが Nuxt.js は初めて触ります。

`nuxt-community/starter-template` と `create-nuxt-app` の違いもよくわからない状態でした。
まぁ、、、[公式](https://ja.nuxtjs.org/guide/installation)を見ると `create-nuxt-app` を使う手順になっており、[nuxt-community/starter-template](https://github.com/nuxt-community/starter-template) は、deprecated になっていたので、今は `create-nuxt-app` を使うんだろう。と思っている程度です。

## 作業環境の情報

```bash:ターミナル
$ npm -v
6.13.2

$ node -v
v12.13.1

$ yarn -v
1.21.0

$ create-nuxt-app -v
create-nuxt-app/2.12.0 darwin-x64 node-v12.13.1
```

## Nuxt(SPA) プロジェクト作成

この辺はささーっと。

```bash:ターミナル
$ create-nuxt-app tutorial

create-nuxt-app v2.12.0
✨  Generating Nuxt.js project in tutorial
? Project name tutorial
? Project description My super-duper Nuxt.js project
? Author name chibi929
? Choose the package manager Yarn
? Choose UI framework Buefy
? Choose custom server framework Express
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint, Prettier
? Choose test framework Jest
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json (Recommended for VS Code)
yarn run v1.21.0
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix
✨  Done in 3.89s.

🎉  Successfully created project tutorial

  To get started:

	cd tutorial
	yarn dev

  To build & start for production:

	cd tutorial
	yarn build
	yarn start

  To test:

	cd tutorial
	yarn test
```

- UI framework: なんとなく Buefy 選択
- Server framework: SPA だけどなんとなく Express を選択
- linting tool: Lint、Formatter が欲しいので ESLint と Prettier 両方を選択

### 起動してみる

```bash:ターミナル
$ cd tutorial
$ yarn dev
yarn run v1.21.0
$ cross-env NODE_ENV=development nodemon server/index.js --watch server
[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): server/**/*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server/index.js`
ℹ Preparing project for development                                                                                                                                                                                                                                         17:33:31
ℹ Initial build may take a while                                                                                                                                                                                                                                            17:33:31
✔ Builder initialized                                                                                                                                                                                                                                                       17:33:31
✔ Nuxt files generated                                                                                                                                                                                                                                                      17:33:31

✔ Client
  Compiled successfully in 7.14s

ℹ Waiting for file changes                                                                                                                                                                                                                                                  17:33:39

 READY  Server listening on http://localhost:3000     
```

ここまでは大丈夫。
Vue CLI 3 以降と違って、CLI で TypeScript 用のプロジェクトが作れないのが辛いだけ。

# TypeScript 対応してみよう

https://typescript.nuxtjs.org/ に記載されている通りに進めるだけ。
しかし、所々で自分はハマってしまったので合わせてメモ。

## [Introduction](https://typescript.nuxtjs.org/guide/)

[Introduction ページ](https://typescript.nuxtjs.org/guide/) を見てみると以下のパッケージが必要そう。

- @nuxt/types
    - これは下2つに含まれているっぽいので敢えてインストールする必要はなさそうだ。
- @nuxt/typescript-build
- @nuxt/typescript-runtime

<img width="746" alt="Introduction" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/ef9d8609-bd2e-acf6-41be-0494b3a6902a.png">

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

[Setup ページ](https://typescript.nuxtjs.org/guide/setup.html) では `@nuxt/typescript-build` のインストール・設定を行う。
やっぱり [Introduction](https://typescript.nuxtjs.org/guide/) に記載されていた `@nuxt/types` は気にしなくて良さそう。
手順通りに以下を行う。

- Installation
- Configuration
    - nuxt.config.js
    - tsconfig.json
    - vue-shim.d.ts

### メモ: Configuration - nuxt.config.js

> // nuxt.config.js
> export default {
>   buildModules: ['@nuxt/typescript-build']
> }

と、記載されていたので buildModules を上記のように変更するのかと思ったが、
上書きではなくて追加するように修正したので、一応メモ。

```diff:nuxt.config.js
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module',
+   '@nuxt/typescript-build'
  ],
```

動作確認のためもう一度 `yarn dev` してみる。
特に問題は出ないはず...

## [Runtime (optional)](https://typescript.nuxtjs.org/guide/runtime.html#installation)

> TypeScript runtime is needed for files not compiled by Webpack, such as **nuxt.config** file, local **modules** and **serverMiddlewares**.

ということなので `nuxt.config.js` を TS 化するときに必要そう。
[Introduction](https://typescript.nuxtjs.org/guide/) に記載されていた `@nuxt/typescript-runtime` は Optional だったということか...
このページは、Optional になっていたので一旦無視する。 ([後で](#runtime-編突入)ハマることになるとは...)

## [Lint](https://typescript.nuxtjs.org/guide/lint.html)

[Lint ページ](https://typescript.nuxtjs.org/guide/lint.html) では、
Introduction では未登場のモジュール( `@nuxtjs/eslint-config-typescript` )のインストールと設定を行う。
手順は Setup と大体同じで以下を行う。

- Installation
- Configuration
    - .eslintrc.js
    - package.json

### メモ: Configuration - .eslintrc.js

> WARNING
> 
> As it will make ESlint use a TypeScript parser ( `@typescript-eslint/parser` ), please ensure `parserOptions.parser` option is not overriden either by you or by another configuration you're extending.
> 
> If you were using `babel-eslint` as parser, just remove it from your `.eslintrc.js` and your dependencies.

という WARNING が記載されていた。
`parserOptions.parser` を見てみると `babel-eslint` が記載されていた。
消さないとダメらしい。一応以下のように修正する。

```diff:.eslintrc.js
- parserOptions: {
-   parser: 'babel-eslint'
- },
  extends: [
    '@nuxtjs',
    'prettier',
    'prettier/vue',
    'plugin:prettier/recommended',
    'plugin:nuxt/recommended',
+   '@nuxtjs/eslint-config-typescript'
  ],
```

### メモ: Configuration - package.json

> "lint": "eslint --ext .ts,.js,.vue ."

と、記載されていたが、
`.ts` だけを追加記載する形にした。

```diff:package.json
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate",
-   "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+   "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
    "test": "jest"
  },
```

とりあえず Lint を実行しておこう。
その結果がこれである。

```bash:ターミナル
$ yarn lint
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing
  44:7   error    Missing space before function parentheses             space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (5 errors, 1 warning)
  3 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

5 errors, 1 warning...

# 激闘の ESLint と Prettier

これからがほんとうの地獄だ...

`--fix` をとりあえず付けてやってみれば少しは改善しそう...的なことが書いてあるからやってみる。

### --fix １回目

```bash:ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:63  warning  Expected no space before '>', but found    vue/html-closing-bracket-spacing
  10:64  error    Insert `/`                                 prettier/prettier
  44:7   error    Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 7 problems (6 errors, 1 warning)
  4 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

6 errors, 1 warning...
増えたやん...

### --fix 2回目

```bash:ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:63  error  Insert `·/`                                prettier/prettier
  44:7   error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (6 errors, 0 warnings)
  4 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

6 errors, 0 warnings...
改善する気配がない...

### --fix 3回目

```bash:ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing
  44:7   error    Missing space before function parentheses             space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (5 errors, 1 warning)
  3 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

Lint Error の内容まで振り出しに戻る...

## 何も考えずに --fix しただけなのでちゃんと見てみる

初めは ESLint と Prettier がバッティングしていることに気づかず苦しみ、
--fix した時の diff などを見ながら何度も実行して苦しみ...

一旦冷静になってエラーの1つだけに着目してみると...

```bash:ターミナル
# 初回
# ESLint さんは self-closing を許していないっぽい
10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing

# １回目の --fix 後
## --fix 効果で self-closing の / が消滅したけどスペースが残ったっぽくて怒ってる ESLint さん
10:63  warning  Expected no space before '>', but found    vue/html-closing-bracket-spacing
## 一方、Prettier さんは self-closing の / を入れろよ！と怒っていらっしゃる
10:64  error    Insert `/`                                 prettier/prettier

# ２回目の --fix 後
## --fix 効果で残ってたスペースが消えたっぽくて ESLint さんの怒りが静まる
## 一方、'スペース' と / を入れろよ！と激怒する Prettier さん
10:63  error  Insert `·/`                                prettier/prettier

# 3回目の --fix 後
## Prettier さんの言い分が通ったのかスペースと / が復活し、振り出しに戻る
10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing
```

とりあえず、自分のスタイルにルールを設定すれば良いんじゃね？
と、考え直す。

### vue/html-self-closing

> Enforce self-closing style
> (self-closing style を強制する)

https://eslint.vuejs.org/rules/html-self-closing.html
self-closing は許容したいので、ESLint さんの設定を変えよう。

> html.void ("never" by default) ... The style of well-known HTML void elements.

この設定が良くなさそうだな。

```diff:.eslintrc.js
  // add your custom rules here
  rules: {
-   'nuxt/no-cjs-in-config': 'off'
+   'nuxt/no-cjs-in-config': 'off',
+   'vue/html-self-closing': ['error', {
+     'html': {
+       'void': 'always'
+     }
+   }]
  }
```

<details>
<summary>折りたたみ (Lint 実行ログ)</summary>
<div>

```bash:ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  44:7  error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 5 problems (5 errors, 0 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
```
</div>
</details>

5 errors, 0 warnings
1個改善！

### @typescript-eslint/no-unused-vars

> Disallow unused variables
> (未使用の変数を許可しない)

https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
変数が未使用なだけで怒るのは、業務でもない限りちょっと怒りすぎだろう。
Warning くらいに変えておこう。

```diff:.eslintrc.js
  rules: {
    'nuxt/no-cjs-in-config': 'off',
    'vue/html-self-closing': ['error', {
      'html': {
        'void': 'always'
      }
-   }]
+   }],
+   'no-unused-vars': 'off',
+   '@typescript-eslint/no-unused-vars': 'warn'
  }
```

<details>
<summary>折りたたみ (Lint 実行ログ)</summary>
<div>

```bash:ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  44:7  error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error    Missing space before function parentheses                                 space-before-function-paren
  60:12  warning  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  warning  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 5 problems (3 errors, 2 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
```
</div>
</details>

3 errors, 2 warnings
よしよし。2つ warning 化。

### space-before-function-paren

> Require or disallow a space before function parenthesis
> (関数の括弧の前にスペースを必要とするか、許可しない)

https://eslint.org/docs/rules/space-before-function-paren

> - `anonymous` is for anonymous function expressions (e.g. `function () {}`).
> - `named` is for named function expressions (e.g. `function foo () {}`).
> - `asyncArrow` is for async arrow function expressions (e.g. `async () => {}`).

自分のスタイルでは以下のようなルールにすれば良いかな。。。

```diff:.eslintrc.js
    // note you must disable the base rule as it can report incorrect errors
    'no-unused-vars': 'off',
-   '@typescript-eslint/no-unused-vars': 'warn'
+   '@typescript-eslint/no-unused-vars': 'warn',
+   'space-before-function-paren': ['error', {
+     'anonymous': 'never',
+     'named': 'never',
+     'asyncArrow': 'always'
+   }]
  }
```

<details>
<summary>折りたたみ (Lint 実行ログ)</summary>
<div>

```bash:ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/nuxt.config.js
  60:12  warning  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  warning  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

✖ 2 problems (0 errors, 2 warnings)

Done in 3.74s.
```
</div>
</details>

自分で設定した warning (no-unused-vars) 以外、消えた！

# TypeScript で記述してみよう

いよいよ TypeScript 編突入である。
型がないと怖くて生きていけない!!

https://typescript.nuxtjs.org/cookbook/components/ を参考に...
元々 Class API スタイルを使おうとしてたが、全部やってみた！

## Options API スタイル

```vue:components/Options.vue
<template>
  <div>
    Name: {{ fullName }}
    Message: {{ message }}
  </div>
</template>

<script lang="ts">
import Vue, { PropOptions } from 'vue'

interface User {
  firstName: string
  lastName: number
}

export default Vue.extend({
  name: 'OptionsAPIComponent',

  props: {
    user: {
      type: Object,
      required: true
    } as PropOptions<User>
  },

  data() {
    return {
      message: 'This is a message'
    }
  },

  computed: {
    fullName(): string {
      return `${this.user.firstName} ${this.user.lastName}`
    }
  }
})
</script>
```

```diff:pages/index.vue
  <template>
      ~略~
+     <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
+ import Options from '~/components/Options'

  export default {
    name: 'HomePage',

    components: {
      Card,
+     Options
    }
  }
  </script>
```
(こちらはあえてそのまま JavaScript スタイルで...)

### メモ: ESLint/Prettier 連合との再戦

`yarn dev` を実行したところエラーが...
Prettier め...

```bash:ターミナル
/tmp/tutorial/components/Options.vue
  2:8  error  Replace `⏎····Name:·{{·fullName·}}⏎····Message:·{{·message·}}⏎··` with `Name:·{{·fullName·}}·Message:·{{·message·}}`  prettier/prettier

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

面倒なのでまたもやとりあえず `--fix`

```diff:components/Options.vue
  <template>
-   <div>
-     Name: {{ fullName }}
-     Message: {{ message }}
-   </div>
+   <div>Name: {{ fullName }} Message: {{ message }}</div>
  </template>
```

これでエラーなくなるならとりあえずはいいか。
そのうちフォーマッターのスタイルは直したいけど。

<img width="714" alt="Options API スタイル" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/9edbda41-e57f-d9b5-daa3-ed83db6e90c3.png">

反映された!!

## Class API スタイル

> Using **[vue-class-component](https://github.com/vuejs/vue-class-component)** through **[vue-property-decorator](https://github.com/kaorun343/vue-property-decorator)**

Composition API は見覚えがないが、
Class API 見覚えがあるので、先にこっちから...
Vue CLI3 で作った TypeScript でも Class-Component Style で触ったデコレーター!

```bash:ターミナル
$ yarn add -D vue-property-decorator
```

```vue:components/Class.vue
<template>
  <div>Name: {{ fullName }} Message: {{ message }}</div>
</template>

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

interface User {
  firstName: string
  lastName: number
}

@Component
export default class ClassAPIComponent extends Vue {
  @Prop({ type: Object, required: true }) readonly user!: User

  message: string = 'This is a message'

  get fullName(): string {
    return `${this.user.firstName} ${this.user.lastName}`
  }
}
</script>
```

```diff:pages/index.vue
  <template>
      ~略~
      <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+     <ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
  import Options from '~/components/Options'
+ import ClassAPIComponent from '~/components/Class'

  export default {
    name: 'HomePage',

    components: {
      Card,
      Options,
+     ClassAPIComponent
    }
  }
  </script>
```

<img width="391" alt="Class API スタイル" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/e22d5918-7bc2-b8d2-c701-e1d51b11283f.png">

２行目の文字列がそれだ。上手くいった!

### メモ: experimentalDecorators

但し、以下のエラーが出ているので tsconfig.json に追加しておこう

```bash:ターミナル
 ERROR  ERROR in /tmp/tutorial/components/Class.vue(14,22):                                                                                                                                                  nuxt:typescript 06:59:04
14:22 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
```

```diff:tsconfig.json
  {
    "compilerOptions": {
      ~略~
+     "experimentalDecorators": true,
      ~略~
  }
```

## Composition API スタイル

> Using **[@vue/composition-api](https://github.com/vuejs/composition-api)** plugin

これは、知らない記法だった。
普段 Vue では前述の Class API スタイルを使っているし使う機会はなさそう...

```bash:ターミナル
yarn add @vue/composition-api
```

```js:plugin/composition-api.ts
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Vue.use(VueCompositionApi)
```

```diff:nuxt.config.js
  /*
   ** Plugins to load before mounting the App
   */
- plugins: [],
+ plugins: ['@/plugins/composition-api'],
```

```vue:components/Composition.vue
<template>
  <div>Name: {{ fullName }} Message: {{ message }}</div>
</template>

<script lang="ts">
import { createComponent, computed, ref } from '@vue/composition-api'

interface User {
  firstName: string
  lastName: number
}

export default createComponent({
  props: {
    user: {
      type: Object as () => User,
      required: true
    }
  },

  setup({ user }) {
    const fullName = computed(() => `${user.firstName} ${user.lastName}`)
    const message = ref('This is a message')

    return {
      fullName,
      message
    }
  }
})
</script>
```

```diff:pages/index.vue
      <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
      <ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+     <Composition :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
  import Options from '~/components/Options'
  import ClassAPIComponent from '~/components/Class'
+ import Composition from '~/components/Composition'

  export default {
    name: 'HomePage',

    components: {
      Card,
      Options,
      ClassAPIComponent,
+     Composition
    }
  }
  </script>
```

<img width="427" alt="Composition スタイル" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/9a3e3e70-3cd8-e16f-1680-702e8b3dcb71.png">

# SPA だけど Express を使ってみよう

```diff:server/index.js
 const express = require('express')
 const consola = require('consola')
 const { Nuxt, Builder } = require('nuxt')
 const app = express()

 // Import and Set Nuxt.js options
 const config = require('../nuxt.config.js')
 config.dev = process.env.NODE_ENV !== 'production'

 async function start() {
+  app.get('/test', (req, res, next) => {
+    const param = { test: 'Hello World!' }
+    res.send(param)
+  })

   // Init Nuxt.js
   const nuxt = new Nuxt(config)
   ~略~
 }
 start()
```

`yarn dev` 後に動作確認

```bash:ターミナル
$ curl http://localhost:3000/test
{"test":"Hello World!"}
```

### メモ: 記述位置は大事らしい

以下の位置に GET API を記述したところ上手くいかなかった

```diff:server/index.js
 ~略~

 async function start() {
   ~略~

   // Give nuxt middleware to express
   app.use(nuxt.render)
+  app.get('/test', (req, res, next) => {
+    const param = { test: 'Hello World!' }
+    res.send(param)
+  })

   ~略~
 }
 start()
```

```bash:ターミナル
# 失敗バージョン
##############################

$ curl http://localhost:3000/test
<!doctype html>
<html >
~略~
```

### メモ: `GET /` もダメらしい

`GET /` を登録してしまうと、ページへのアクセスができなくなる。
そりゃそうか。

### メモ: this.$axios が使えない

```diff:component/Class.vue
    ~略~
+   mounted() {
+     this.$axios
+       .get('/test')
+       .then((res) => {
+         console.log(res)
+       })
+       .catch((err) => {
+         console.log(err)
+       })
+   }
  }
  </script>
```

```bash:ターミナル
 ERROR  ERROR in /tmp/tutorial/components/Class.vue(25,10):                                                                                                                                                  nuxt:typescript 20:58:26
25:10 Property '$axios' does not exist on type 'ClassAPIComponent'.
    23 |   mounted() {
    24 |     console.log('mounted')
  > 25 |     this.$axios
       |          ^
    26 |       .get('/test')
    27 |       .then((res) => {
    28 |         console.log(res)
```

tsconfig.json に追記が必要

```diff:tsconfig.json
  {
    "compilerOptions": {
      ~略~
      "types": [
        "@types/node", 
        "@nuxt/types", 
+       "@nuxtjs/axios"
      ]
    },
```

# Store を TypeScript で書いてみよう

https://typescript.nuxtjs.org/cookbook/store.html

とりあえず [Vanilla JS](https://typescript.nuxtjs.org/ja/cookbook/store.html#vanilla) の方で良いや...と...
ちなみにここも自分の中では激ハマりポイントである。
上記のページを参考に `store/index.ts` を準備できたら、
任意の Component の `mounted()` でログを出してみる。

```ts
mounted() {
  console.log(this.$store.state.counter)
}
```

```bash:ターミナル
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
    at VueComponent.mounted (Class.vue?e831:34)
```
しかし、どうも `this.$store` が undefined みたい...

次に、[Nuxt.js](https://ja.nuxtjs.org/guide/vuex-store#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%83%A2%E3%83%BC%E3%83%89) のページを参考に...
普通の Vuex... というわけではないが `Nuxt Typescript` よりは普通に近い形で、型に怒られたら any でにげる。

```ts:store/index.ts
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state: any) {
    state.counter++
  }
}
```

```bash:ターミナル
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
    at VueComponent.mounted (Class.vue?e831:34)
```
しかし、やっぱり `'state' of undefined`。
どうにも `this.$store` が未定義状態だなー...

試しに JS ファイルにしてみると...

```ts:store/index.js
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}
```

動くやん...?

# Runtime 編突入

でもやっぱり TypeScript で書きたい！
TypeScript のセットアップの中で、ひとつだけ js 系を ts 系に置き換えるためのものなのに
やっていないものを思い出す。そう、オプショナルだった[Runtime](#runtime-optional)...

> TypeScript runtime is needed for files not compiled by Webpack, such as **nuxt.config** file, local **modules** and **serverMiddlewares**.

と書いてあったものなので、
Store 系にも影響するかはわからない。けどやってみよう。そう思った。

そして、もう一度[Runtime](https://typescript.nuxtjs.org/guide/runtime.html)ページでセットアップ方法を確認する。
内容は Setup とほとんど同で以下の通りである。

- Installation
- Usage
    - package.json

モジュールのインストールは特に問題ない。ひとつだけ注意するとしたら

> TIP
> 
> Note that this package is installed as `dependency` and not `devDependency` like `@nuxt/typescript-build`, cause `@nuxt/typescript-runtime` is needed for production

`devDependencies` には入れちゃいけない。

```bash:ターミナル
yarn add @nuxt/typescript-runtime
```

`package.json` の書き換えは結構困った。
[ドキュメント](https://typescript.nuxtjs.org/guide/runtime.html#usage)によると nuxt-ts コマンドに書きかえろよ。と...

```json:package.json
{
  ~略~
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "generate": "nuxt-ts generate",
    "start": "nuxt-ts start"
  },
  ~略~
}
```

しかし、自分の package.json は `build` と `generate` は良いが...
`dev` と `start` には cross-env なるものが使われている...
果たして以下のように全部書き換えて良いものなのか？

```diff:package.json
  {
    ~略~
    "scripts": {
-     "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+     "dev": "nuxt-ts",
-     "build": "nuxt build",
+     "build": "nuxt-ts build",
-     "start": "cross-env NODE_ENV=production node server/index.js",
+     "start": "nuxt-ts start"
-     "generate": "nuxt generate"
+     "generate": "nuxt-ts generate"
    },
    ~略~
  }
```

とりあえず、なんか怖いと思ったので `dev` と `start` は変えないことにした。
cross-env を使ったまま `server/index.js` を起動する形のまま置いておく。

```diff:package.json
  {
    ~略~
    "scripts": {
      "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
-     "build": "nuxt build",
+     "build": "nuxt-ts build",
      "start": "cross-env NODE_ENV=production node server/index.js",
-     "generate": "nuxt generate"
+     "generate": "nuxt-ts generate"
  },
  ~略~
}
```

とりあえず `yarn dev` を実行しても問題なさそう。
いよいよ `store/index.js` を TypeScript に置き換えてみる
~~と、言っても any の型を付けるだけである~~

```diff:store/index.ts
 export const state = () => ({
   counter: 0
 })

 export const mutations = {
-  increment(state) {
+  increment(state: any) {
     state.counter++
   }
 }
```

`yarn dev` を実行してみると、どうやら `this.$store` はまだ読み込めていない。
しかたなく `nuxt-ts` を実行してみると...
`this.$store` が読み込め、 `counter` の値が表示できた。

さて、ここで問題なのは
`nuxt-ts` で動かした時に `Express` で作成した API はどうなるのか？
`server/index.js` と `nuxt` コマンドは何をやっているのかを追ってみることにした。

### server/index.js が何をしているのか？

`server/index.js` は短いコードなのですぐ読める。

Nuxt インスタンスを生成して `Builder.build` を実行する。
だったり `Nuxt.ready` を実行する。
ついでに、Express の API を使う感じだ。

### nuxt コマンドが何をしているのか？

[@nuxt/cli](https://github.com/nuxt/nuxt.js/tree/dev/packages/cli) を追ってみると色々汎用的にガチャガチャやってるが、
結局は [cli-dev](https://github.com/nuxt/nuxt.js/blob/dev/packages/cli/src/commands/dev.js) で `Builder.build` したり `Nuxt.ready` を実行しているだけである。

つまり答えは、 `server/index.js` を動かしてないから、
単純に Express が立っていない状態になるのである。たぶん！！

```bash:ターミナル
$ curl http://localhost:3000/test
<!doctype html>
<html >
```

## 安心して nuxt-ts に書き換えよう

```diff:package.json
 {
   ~略~
   "scripts": {
-    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+    "dev": "nuxt-ts",
     "build": "nuxt-ts build",
-    "start": "cross-env NODE_ENV=production node server/index.js",
+    "start": "nuxt-ts start"
     "generate": "nuxt-ts generate"
   },
   ~略~
 }
```

こうして、Express を犠牲にして、
Store を TypeScript で書くことができた。

# ServerMiddleware を使ってみよう

さて、そしたら次に気になってしまうのは
Nuxt.js で TypeScript を使ったとき、Express はどうやって使うのか。

[Nuxt.js v2.9にTypeScriptとExpress.jsを対応してみた](https://qiita.com/sauzar18/items/d68c13f200b3bc671679)
[API: serverMiddleware プロパティ](https://ja.nuxtjs.org/api/configuration-servermiddleware)

を参考にした。

`nuxt.config.js` に `serverMiddleware` の項目を追加すれば良いらしい。
まずは `server/index.js` を書き換えよう。

```ts:server/index.ts

import express from 'express'
import bodyParser from 'body-parser'
const app = express()

app.use(bodyParser.json())
app.get('/test', (req, res, next) => {
  const param = { test: 'Hello World!' }
  res.send(param)
})

export default app
```

Nuxt 系の処理が全部不要(nuxt-ts コマンドに任せた)なので、
ごっそり消して Express の処理だけ書きます。
Express のインスタンスは export しておきます。

次に `nuxt.config.js` に `serverMiddleware` の項目を追加します。

```diff:nuxt.config.js
 {
   ~略~
+  serverMiddleware: [
+    { path: '/api', handler: '@/server/index.ts' }
+  ]
 }
```

ts ファイルが直接設定できるんだなー。
きっと [Runtime 編](#runtime-編突入)を突破したおかげですね！

```bash:ターミナル
$ curl http://localhost:3000/api/test
{"test":"Hello World!"}
```

実行してみると普通に叩けました。

# Middleware を使ってみよう

[API: serverMiddleware プロパティ](https://ja.nuxtjs.org/api/configuration-servermiddleware) を参考してたら `Middleware` も目に入ってしまった。

[API: middleware プロパティ](https://ja.nuxtjs.org/api/pages-middleware)
[ルーティング - ミドルウェア](https://ja.nuxtjs.org/guide/routing#%E3%83%9F%E3%83%89%E3%83%AB%E3%82%A6%E3%82%A7%E3%82%A2)

どうやら、ルーティング時に呼び出すことができるものらしい。
`sereverMiddleware` 同様、 `nuxt.config.js` と `ソースコード` をちょちょっと触るだけみたい。

## ルートが変更された時に呼ばれる Middleware

```ts:middleware/hoge.ts
export default async function() {
  console.log('Middleware: hoge')
}
```

```diff:nuxt.config.js
   serverMiddleware: [
     { path: '/api', handler: '@/server/index.ts' }
   ],
+  router: {
+    middleware: 'hoge'
+  }
 }
```

これだけで、ルーティング変更時に毎度 `console.log('Middleware: hoge')` が実行されるのである。

## 特定のルートに変更された時に呼ばれる Middleware

前述の `nuxt.config.js` に書く Middleware は、
全てのルートにおいて、呼び出し時に動く Middleware である。
特定のページに行った時のみ実行したい場合はどうするか。

以下の通りである。
とりあえず `hoge.ts` を使うとわかりづらくなってしまうので `foo.ts` を用意する。

```ts:middleware/foo.ts
export default async function() {
  console.log('Middleware: foo')
}
```

次にルーティング対象の `pages` 以下に手を入れる

```diff:pages/inspire.vue
 ~略~
 </template>
+ <script lang="ts">
+ import { Vue, Component, Prop } from 'vue-property-decorator'
+ 
+ @Component({
+   middleware: 'foo'
+ })
+ export default class Inspire extends Vue {}
+ </script>
```

これで、inspire.vue ルーティングを変更したときに foo が呼び出される。

> これで stats ミドルウェアはすべてのルート変更時に呼び出されるようになります。
> 同様に、特定のレイアウトもしくはページ内にもミドルウェアを追加することができます:

注意点としては[ルーティング - ミドルウェア](https://ja.nuxtjs.org/guide/routing#%E3%83%9F%E3%83%89%E3%83%AB%E3%82%A6%E3%82%A7%E3%82%A2)に記載されている通り、
 `layouts` もしくは `pages` のものしか使えなそう。
[ここで試している Class API スタイル](#class-api-スタイル)の Component に `middleware` を追加してみたけど、
foo は実行されなかった。

# Store にデコレーターを使ってみよう

さて Store は最初 Vanilla JS を使ってしまいましたが、
[vuex-module-decorator](https://github.com/championswimmer/vuex-module-decorators) を使った記述が推されているっぽい。


まずはモジュールをインストールしておく

```bash:ターミナル
yarn add -D vuex-module-decorators
```

そして [Store | Nuxt TypeScript](https://typescript.nuxtjs.org/cookbook/store.html#class-based) と [Module re-use, use with NuxtJS](https://github.com/championswimmer/vuex-module-decorators#module-re-use-use-with-nuxtjs) を参考に Store を書き換えてみることにする

```ts:store/example.ts
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'

@Module({ stateFactory: true })
class MyModule extends VuexModule {
  counter = 0

  @Mutation
  increment() {
    this.counter++
  }
}
```

```ts:utils/store-accessor.ts
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import example from '~/store/example'

let exampleStore: example

function initialiseStores(store: Store<any>): void {
  exampleStore = getModule(example, store)
}

export {
  initialiseStores,
  exampleStore,
}
```

```ts:store/index.ts
import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export * from '~/utils/store-accessor'
```

### メモ: example が見つからない

```bash:ターミナル
ERROR in /tmp/tutorial/utils/store-accessor.ts(3,8):                                                                                                                                                 nuxt:typescript 23:40:46
3:8 Module '"/tmp/tutorial/store/example"' has no default export.
    1 | import { Store } from 'vuex'
    2 | import { getModule } from 'vuex-module-decorators'
  > 3 | import example from '~/store/example'
```

ドキュメント通りにやるとエラーになる。
`store/example.ts` は export してないから当然か。

```diff:store/example.ts
  @Module({ stateFactory: true })
- class MyModule extends VuexModule {
+ export default class MyModule extends VuexModule {
    counter = 0
```

### メモ: module name が設定されていない

意気揚々と `yarn dev` !!
http://localhost:3000 をブラウザで開いてみると
なんと真っ白！

Developer Tool を開いて確認すると

```
Error: ERR_GET_MODULE_NAME : Could not get module accessor.
      Make sure your module has name, we can't make accessors for unnamed modules
      i.e. @Module({ name: 'something' })
    at getModuleName (index.js?6fc5:24)
    at getModule (index.js?6fc5:41)
    at initialiseStores (store-accessor.ts?8dac:8)
    at initializer (index.ts?598a:3)
    at eval (vuex.esm.js?2f62:353)
    at Array.forEach (<anonymous>)
    at new Store (vuex.esm.js?2f62:353)
    at createStore (store.js?6c6b:42)
    at createApp$ (index.js?f26e:51)
    at tryCatch (runtime.js?96cf:45)
```

`@Module({ name: 'something' })` のように設定してくれ。とのこと。
`vuex-module-decorator` 使うと名前空間付きモジュールしか設定できないのかなー？

```diff:store/example.ts
- @Module({ stateFactory: true })
+ @Module({
+   stateFactory: true,
+   namespaced: true,
+   name: 'example'
+ })
  export default class MyModule extends VuexModule {
    counter = 0
```

のように書き換えて再度実行！
任意の Component では以下のようなログ出力を行なっています。

```ts
import { exampleStore } from '~/store'

mounted() {
  console.log(this.$store.state.counter)
  console.log(this.$store.state.example.counter)
  console.log(exampleStore.counter)
}

// => undefined
// => 0
// => 0
```

やっぱり名前空間付きモジュールになっている。
ちなみに `store/example.ts` の `namespaced: true` を `namespaced: false` に書き換えてもダメでした。
`vuex-module-decorator` を使うとルートモジュールは作れないのかな？

ひとまず `store/index.ts` を以下のように書き換えたら
ルートモジュールの `store/index.ts` 及び、
名前空間付きモジュールの `store/example.ts` の両方が使えるようになりました。

```diff:store/index.ts
  import { Store } from 'vuex'
  import { initialiseStores } from '~/utils/store-accessor'
  const initializer = (store: Store<any>) => initialiseStores(store)
  export const plugins = [initializer]
  export * from '~/utils/store-accessor'
+ 
+ export const state = () => ({
+   counter: 0
+ })
+ 
+ export const mutations = {
+   increment(state: any) {
+     state.counter++
+   }
+ }
```

# まとめ

これだけ一通りやって、たくさん躓き、
後輩の雑用を軽減するアプリを作成しました。

ちなみに、この Nuxt プロジェクトは、最初に作成している通り、
SPA なので、最終的には `nuxt-ts generate` を実行して静的ホスティングをしております。
つまり、無駄に使った serverMiddleware は動いていません。
まぁ、動いても本記事に書いたレベルの `{"test":"Hello World!"}` 程度の戻り値が出るだけなんですけどね。

しかし、実験コードが入れっぱなしなので、
Developer Tool を開くと以下のエラーが出ているオマケ付きです。
~~まぁ、後輩が使うだけだし、悪影響ないし、ヨシ~~

```
http://example.com/api/test net::ERR_CONNECTION_REFUSED
```

ということで、

- 一番ハマるのは Linter + Prettier
- Runtime は Optional だけど TypeScript を使うなら入れておけ
- `$axios` もそうだが、時々型定義でハマる (本記事には書いてないけど `$buefy` も型定義エラーになった)
- 何かやろうとすると多少なりとも動かない！

[やってないこと]

- sass