【備忘録】Nuxt.jsをtypescript化してみた

# はじめに

nuxt.jsでプロダクト開発をしていて流石にjsが厳しくなってきたので、typescript版Nuxt.jsの雛形を作りました。
備忘録的に作業を記録しておきます。

## nuxt.jsの雛形作成

```bash
$ yarn create nuxt-app <プロジェクト名/今回はnuxt-tsとしました>

create-nuxt-app v2.9.2
✨  Generating Nuxt.js project in nuxt-ts/
? Project name nuxt-ts
? Project description My geometric Nuxt.js project
? Author name taro
? Choose the package manager Yarn
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework Jest
? Choose rendering mode Universal (SSR)

$ cd nuxt-ts
```

## typescript化

公式ドキュメントでも推奨されていたので[nuxt-property-decorator](https://github.com/nuxt-community/nuxt-property-decorator)を採用。

```
$ yarn add -D @nuxt/typescript
$ yarn add ts-node nuxt-property-decorator
$ touch tsconfig.json
$ mv nuxt.config.js nuxt.config.ts
```

各種ファイルを変更

```diff:pages/index.vue
-<script>
-import Logo from '~/components/Logo.vue'
+<script lang="ts">
+import { Component, Vue } from "nuxt-property-decorator";

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

```diff:components/Logo.vue
+<script lang="ts">
+import { Vue } from "nuxt-property-decorator";
+export default Vue.extend({})
+</script>
```

起動して確認。以外にあっさり完了しました^^

```bash
$ yarn dev
```

## Vuexのtypescript化
続いてVuexです。
[vuex-module-decorators](https://github.com/championswimmer/vuex-module-decorators)を採用しました。(考察あり後述）

```bash
$ yarn add vuex-module-decorators
```

```ts:store/index.ts
import Vuex, { Store } from "vuex";

interface RootState {}
export const store = new Vuex.Store<RootState>({
  actions: {
    nuxtServerInit: () => {}
  }
});
const createStore = (): Store<RootState> => {
  return store;
};
export default createStore;
```

参考までにvuex-module-decoratorsを使うとこんな感じで書けます。

```ts:store/pages/index.ts
import { Mutation, Action, VuexModule, getModule, Module} from "vuex-module-decorators";
import { store } from "~/store";

interface IndexPageState {
  name: string;
}

@Module({ dynamic: true, store, name: "IndexPage", namespaced: true })
class IndexPage extends VuexModule implements IndexPageState {
  // state
  name: string = "";

  // mutations
  @Mutation
  private SET_NAME(name: string) {
    this.name = name;
  }

  // getters
  get name() {
    if (this.user == null) return "null";
    return this.user.name;
  }

  // actions
  @Action({})
  public fetch() {
    this.SET_NAME('hoge');
  }
}
export default getModule(IndexPage);
```

componentからはこのように使います。

```vue:pages/index.ts
<template>
  <div class="container">
    <p @click="test">{{ name }}</p>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Vue } from "nuxt-property-decorator";
import PageStore from "~/store/pages/index";

@Component({
  async asyncData() {
    await PageStore.fetch();
  }
})
export default class IndexPage extends Vue {
  get name(): string {
    return PageStore.name;
  }

  @Emit()
  private test() {
    console.log(`test -> ${PageStore.name}`);
  }
}
</script>
```

これでVSCodeでインテンスも効いて快適になりました。
こちらの[記事](https://qiita.com/tsrnk/items/fd95c3d8013d0795a260
)を参考にさせていただきました。ありがとうございます。

これで一件落着と思いきや、ここで１つ問題発生です。
vuex-module-decoratorsを採用したことでstoreをクラシックモードで記述することになります。
しかし、Nuxtのv3ではクラシックモードのサポートが切れるようなのでどうしたもんかなと...
以下のようなwarningが出ます。

```bash
 WARN  Classic mode for store/ is deprecated and will be removed in Nuxt 3.
```

この件について[こちら](https://github.com/nuxt/nuxt.js/issues/4912)でも議論されていました。

今後は[vuex-orm](https://github.com/vuex-orm/vuex-orm)を使うのが良さそうと言う話ですが、
まだキャッチアップできてないので調べたら記事にしようと思います。
SSRでの実装方法も[こちら](https://github.com/vuex-orm/plugin-graphql/blob/master/docs/guide/nuxt.md)に例がありましたので貼っておきます。

## Eslint/Pretter

とりあえず一通りtypescript化できたので、次はEslitの設定を諸々進めます。

```js:.eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    project: './tsconfig.json',
    ecmaFeatures: { "legacyDecorators": true }
  },
  extends: [
    '@nuxtjs',
    'plugin:nuxt/recommended',
    'plugin:prettier/recommended',
    'prettier/vue',
    'prettier/@typescript-eslint'
  ],
  plugins: [
    'prettier',
    '@typescript-eslint'
  ],
  // add your custom rules here
  rules: {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "error"
  }
}
```

上記に必要なパッケージ群をインストール

```
$ yarn add -D eslint@5.15.1 // eslintだけうまく入らなかったのでversion指定してあげました
$ yarn add -D eslint-plugin-nuxt @nuxtjs/eslint-config @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier
```

eslintのaliasを設定

```diff:package.json
  "scripts": {
+    "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."
  },
```

実行！

```bash
$ yarn lint --fix
```

# まとめ
storeのクラシックモード問題があったり、まだまだ覚束無い感じですがtypescript化出来ました。
Vuex周りで何かいい解決方法お持ちの方いたら教えてくださいmm

備忘録で説明適当だったので、作業リポジトリのリンクを貼っておきます。
興味ある方は[こちら](https://github.com/yoshinbo/nuxt-ts)からご覧ください。

では。