TypeScript版Nuxtであるnuxt-tsに移行してJSファイルを駆逐した

(追記: 2019年3月27日)
v2.5.0が3月21日にリリースされました。
このバージョンではこの記事の内容はあわないところがでてきます。
私が使用しているGAE環境ではv2.5がいまのところ動かないので、その不具合が解消されたらv2.5についての記事を別に書こうと思っています。

(追記: 2019年4月5日)
v2.6.0がリリースされたので、予告通り別記事を投稿しました。
[待望のNuxt v2\.6リリース\! 〜 nuxt\-tsからの移行編〜](https://qiita.com/iwata@github/items/f798cc6cc24ac41a74d9)

## Nuxt.js v2.4.0のTypeScript対応
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Nuxt v2.4.0 is finally here 🎉<br>Official TypeScript support, smart prefetching and many more features &amp; bug fixes.<a href="https://t.co/Ljf29xYvXi">https://t.co/Ljf29xYvXi</a></p>&mdash; Nuxt.js (@nuxt_js) <a href="https://twitter.com/nuxt_js/status/1089948593392365568?ref_src=twsrc%5Etfw">January 28, 2019</a></blockquote>

つい先日Nuxt.js v2.4.0がリリースされました。
以下リリースブログからの抜粋。

> TypeScript support has landed!
> In order to run Nuxt with TypeScript, we created a new distribution, called nuxt-ts.
> ⚠️ Experimental: We are waiting for your feedback to keep improving it and breaking changes can occur without a semver major release. However, all changes will be documented properly

まだ実験段階ですがTypeScriptのサポートが強化されました。
~~TypeScriptサポートのNuxtを使うためには[nuxt-ts](https://www.npmjs.com/package/nuxt-ts)を使う必要があります。~~ nuxtでも`ts-loader`不要でTypeScript使えるようになりました。

## `nuxt`と`nuxt-ts`との違い


- `nuxt`コマンドではなく`nuxt-ts`コマンドを使う必要がある
    - `build`, `start`など
- Run TimeがTypeScriptをサポート
    - `nuxt.config.js`ではなく`nuxt.config.ts`を書く必要がある
    - `module`, `serverMiddleware`もTypeScriptで書ける
- `build.useForkTsChecker`がデフォルトでtrue
    - https://nuxtjs.org/api/configuration-build#useforktschecker

より詳しくは[TypeScript Support \- Nuxt\.js](https://nuxtjs.org/guide/typescript#codefund_ad)を参照してください。

## `tsconfig.json`

`tsconfig.json`がまだ存在しない場合は、`nuxt-ts`コマンドの初回実行時に作成してくれます。
主力されるのは以下になります。

```json:tsconfig.json
{
  "extends": "@nuxt/typescript",
  "compilerOptions": {
    "baseUrl": ".",
    "types": [
      "@types/node",
      "@nuxt/vue-app"
    ]
  },
}
```

`@nuxt/typescript`内の[tsconfig.json](https://github.com/nuxt/nuxt.js/blob/dev/packages/typescript/tsconfig.json)を参照しているのと、`types`に`@nuxt/vue-app`が追加されています。
~~このため`@types/xxx`の自動インクルードが無効化されてしまうので、`@types`を追加した場合は自分で明示的に`tsconfig.json`にも追記する必要がでてきます。[^1]~~
`types`ではなく、`typeRoots`を以下のようにすることで明示的に追記する必要なくできます。

```json
{
  "extends": "@nuxt/typescript",
  "compilerOptions": {
    "baseUrl": ".",
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@nuxt/vue-app/types"
    ]
  },
}
```

[^1]: 正直イケてないので理想的には`@types/nuxt`に移動してほしいところ。monorepoの観点からいまはこうしているようですが…

## type safeに`nuxt.config.ts`を書く

`nuxt.config`内の[build.extend](https://nuxtjs.org/api/configuration-build#extend)を書くときの話です。
JSだと以下のように書きますね。

```javascript:nuxt.config.js
export default {
  build: {
    extend (config, { isClient }) {
      // Extend only webpack config for client-bundle
      if (isClient) {
        config.devtool = '#source-map'
      }
    }
  }
}
```

このとき`extend`の引数をtype safeにしたい場合、型定義を`import`する必要があります。
第一引数はWebpackのConfigurationなのであらかじめWebpackの型定義をinstallしておきます。

```sh
$ yarn add @types/webpack
```

~~`tsconfig.json`にも追記します。~~
`typeRoots`を使うことで追記は不要にできます。


```diff:tsconfig.json
    "types": [
       "@types/node",
+      "@types/webpack",
       "@nuxt/vue-app"
    ]
```

そうすると`nuxt.config.ts`は`any`を使わず以下のように書けます:white_flower: 

```typescript:nuxt.config.ts
import {Configuration} from 'webpack'
import {Context} from '@nuxt/vue-app'

export default {
  build: {
    extend (config: Configuration, { isClient }: Context) {
      // Extend only webpack config for client-bundle
      if (isClient) {
        config.devtool = '#source-map'
      }
    }
  }
}
```

## `nuxt-ts`移行時にハマった3つのこと

以下、既存プロジェクトで`nuxt-ts`化したときにハマったことを書いてみました。
躓いている方がいれば参考にしてください。

前提として既存プロジェクトでは、すでにTypeScriptを使っており、よくある`ts-loader`の設定を自前で[^3]書いていました。

[^3]: [TypeScriptでNuxtアプリケーションを作成する際の覚書 \| Studio Andy](https://blog.andoshin11.me/posts/nuxt-with-typescript)など

### Custom Server Middleware

前述したように`nuxt-ts`を使えば`serverMiddleware`もTypeScriptで書けます。
既存プロジェクトでCookie処理を[Custom Server Middleware](https://nuxtjs.org/api/configuration-serverMiddleware#custom-server-middleware)として記述していたのですが、それをTypeScriptに移行するときにハマりました。

結論としてServer SideではCommonJSで書く必要があるということです。

`nuxt-ts`コマンドは中で[@nuxt/typescript](https://github.com/nuxt/nuxt.js/blob/dev/packages/typescript/src/index.js)を使っています。[実装をみる](https://github.com/nuxt/nuxt.js/blob/dev/packages/typescript/src/index.js#L45-L52)と`compilerOptions.module: "commonjs"`が指定されています。この変更はv2.4.2で入りました。[^4]なので、`module.exports`と`require`を使う必要があります。
この辺りは正式にNuxt.jsのmasterとしてマージされたときに変更されるなり、ドキュメント化されるなりすると思いますがハマりました。

[^4]: [fix\(ts\): fix typescript default module compilerOption by kevinmarrec · Pull Request \#4886 · nuxt/nuxt\.js](https://github.com/nuxt/nuxt.js/pull/4886)

### `@types/xxx`は`devDependencies`ではなく、`depencencies`に宣言する

`package.json`の話です。
前述のようにServer SideでもTypeScriptを使う[^5]ため、`tsconfig.json`は`nuxt-ts start`時でも必要になります。
`ts-loader`だけで使っていたときは`build`時にのみ必要だったので`devDependencies`として宣言していました。
`NODE_ENV=production`環境でエラーになると思っていたら、`tsconfig.json`の処理で型定義が参照できないというエラーが起きていました。

[^5]: [ts-node](https://www.npmjs.com/package/ts-node)を使って実現しています

### Jest利用時

テスティングフレームワークはJest[^6]を使っています。
JestでTypeScriptを使うために[ts-jest](https://www.npmjs.com/package/ts-jest)を使っており、どうやらこれが`tsconfig.json`の`extends`で`node_modules`以下を使うとうまく参照してくれないようです。それでJest実行時だけ必要な設定が有効にならず、エラーとなっていました。
いまは冗長ですが、`@nuxt/typescript`の設定を全展開しています。

[^6]: まだv23.xを使っています。最新のv24だとTypeScript周りの使い方が変っており、まだうまく動かなかったため

### Google App Engine Standard Environment Nodeで動かせない

SSRの実行環境としてGAE SE Nodeを使っています。
大したアプリでなかったので最弱の`machine_type`であるF1で動かしていたのですが、`nuxt-ts`だとF2以上でないと動きませんでした。
ワークロードが重くなっているようなので、この辺り正式リリース時には改善されることを期待したいです。

## さいごに

紆余曲折ありましたが、いまでは本番環境で問題なく動いています:tada: 
最終的にプロジェクト以下のJSファイルは以下2つのみにできました:exclamation: 

```sh
$ fd -e js
babel.config.js
jest.config.js
```