Nuxt.js+ExpressのプロジェクトをTypeScript化する
Tags: nuxt, vue, express, typescript
Published: 2018-08-27 12:10:20

---
title: Nuxt.js+ExpressのプロジェクトをTypeScript化する
published: true
description: 
tags: nuxt,vue,express,typescript
canonical_url: https://crieit.net/posts/Nuxt-js-Express-TypeScript
---

create-nuxt-appでNuxt.js+Expressのプロジェクトは簡単に作ることができますが、それで作成したプロジェクトをTypeScript化してみました。

TypeScript化の対応は、Nuxt.js側、Express側で別々となります。Express側はちょっと面倒なので、とりあえずNuxt.jsだけ先にやっておく、とかでも良いと思います。

Nuxt.js+Expressプロジェクトの作成は下記で紹介しているexpress-templateを利用おり、今回の記事はそちらの前提の説明となります。

[Nuxt+Expressのプロジェクト作成で良さそうなのは？](https://crieit.net/posts/Nuxt-Express)

※ 2018/8現在の情報です。

## Nuxt.js側をTypeScript化する

基本的には、create-nuxt-appのtypescript-templateで実装されているものを参考にしています。

[nuxt-community/typescript-template: Typescript starter with Nuxt.js](https://github.com/nuxt-community/typescript-template)

### TypeScript関連をインストール

とりあえずTypeScriptをインストールします。

```sh
yarn add --dev typescript ts-loader@3.5.0
```

ts-loaderは3.5.0のバージョンを指定します。無指定だとこのプロジェクトに合わないバージョンが入ってエラーになってしまいます。

あとはNuxt.jsとVueをTypeScriptと連携させるライブラリも入れておきます。

```sh
yarn add nuxt-property-decorator vue-property-decorator
```

### 設定ファイルを準備

先程の [typescript-template](https://github.com/nuxt-community/typescript-template) のリポジトリから、下記のファイルをそのままコピーしてきます。

* index.d.ts
* tsconfig.json
* modules/typescript.js

そして、nuxt.config.jsのmodulesに`"~/modules/typescript.js"`を追加しておきます。

基本的にはこれで完了です。あとは実際に各コンポーネントをTypeScriptにしていくだけです。

### VueコンポーネントをTypeScript化する

とりあえず簡単なコンポーネントのサンプルです。JavaScriptのままでも動きますので、必要なコンポーネントから順次TypeScriptに変更していくことも可能です。

```html
<template>
  <div>
    {{ name }}
    <button @click="increment">Click</button>
    {{ cnt }}
    <sub-component></sub-component>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "nuxt-property-decorator";
import { Prop } from "vue-property-decorator";
import SubComponent from "./SubComponent.vue";

@Component({
  components: {
    SubComponent
  }
})
export default class extends Vue {
  @Prop({ type: String, required: true })
  name: string;
  
  cnt = 0;

  increment() {
    this.cnt++;
  }
}
</script>
```

#### scriptタグ

scriptタグは`lang="ts"`を追記します。

#### Vueクラスを使う

JavaScriptの場合はオブジェクトをexportしていましたが、TypeScriptの場合はclassを使うため、Vueクラスを継承したクラスを作成してそれをexportします。

```javascript
export default class extends Vue {
```

#### Componentデコレータを使う

TypeScriptの場合は、クラス内にコンポーネントの設定を行うのではなく、`@Component`デコレータを追加してそちらに設定を追加します。

```javascript
@Component({
  components: {
    SubComponent
  }
})
```

#### ステートはプロパティとして定義

コンポーネントが保持するステートは`data`メソッドを使う必要がなく、直接クラスのプロパティとして定義します。もちろん型も指定できます。配列やオブジェクトの場合は型を指定しておかないとメソッド内で使う場合にエラーになったりします。

```javascript
cnt = 0;
item: Item;
```

#### プロパティはPropを使う

呼び出し元から渡されるVueコンポーネントのプロパティは`@Prop`を使ってクラスのプロパティとして定義します。`@Prop`は`@Prop(Number)`のようにもできますし、色々と指定方法があります。

#### メソッドはクラスのメソッド

その他はだいたい一緒です。メソッドはクラスのメソッドになるため、カンマ等で区切る必要はありません。

## Express側をTypeScript化する

Express側はちょっと面倒なので、気分が乗ったらやる、くらいの感じでも良いかも知れません。シンプルなルーティングの処理がメインだと恩恵も少ないかもしれません。

そもそも開発時とリリースビルド時でも処理が異なるため、両方で動かせるように気をつけながら対応していく必要があります。また、色々な方法がありそうだったため、拘る人は自分で色々と調べて実装してみたほうが良いかも知れません。今回はとりあえずやってみて動いた方法を紹介します。

ちなみに本番環境の手順については、一応ローカルでは動いていますが実際の本番環境では未構築のためまだ何か問題が出る可能性もありますのでご注意ください。何か見つかり次第等記事に追記していきます。

### 開発時の設定

前述のテンプレートだと、開発時はnodemonを利用してサーバーを起動＆ホットリロードを実現しています。開発時はある程度適当でも問題ないと思いますので、nodemonでts-nodeを使えるようにします。ts-nodeというのはNodeのスクリプトを実行する時に、直接TypeScriptを実行できるようにするコマンドです。

#### ts-nodeを設定

ts-nodeの設定は下記を参考にしました。

[nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。](http://aknow2.hatenablog.com/entry/2018/04/09/150006)

まずはts-nodeをインストールします。

```sh
yarn add --dev ts-node
```

nodemonでtsファイルを使えるように設定ファイルを追加します。

nodemon.json

```json
{
    "watch": ["server", "routes"],
    "ext": "ts",
    "exec": "ts-node ./server/index.ts"
}
```

package.jsonの`dev`スクリプトで、`nodemon server/index.js`としている部分を`nodemon server/index.ts`のように拡張子を変更します。

あとは`server/index.js`の拡張子をtsに変更すればOKです。

基本的にはこれで`dev`スクリプトは動くと思うのですが、他の各ファイルのグローバルな場所で同じ変数（router等）が定義されているとエラーになってしまうようなので、クラス化したり変数名を変えるか、下記のようにファイル全体を中括弧で囲んでおくかします。

```javascript
{
  const { Router } = require("express");
  const router = Router();

  router.get("/logout", function(req, res) {
    req.logout();
    res.redirect("/");
  });

  app.use("/auth", router);
}
```

#### 他のファイルもTypeScript化する

その他のルーティング用ファイルなども、基本的には拡張子をtsに変更すれば動きます。`server/index.ts`からそれらの読み込む場合などに、拡張子の指定が必要になります。

あとは定義されていない変数などを使用するとエラーとして検出してくれたりしますので、使っていない処理などがあってエラーになってしまう場合は適当に修正しておいてください。

また、オブジェクトも適当なプロパティを途中で追加したりするとエラーになりますので、変数を定義する場合にany等をつけておいてください。

```typescript
const where: any = {
  deleted_at: null
};
where.keyword = req.params.keyword;
```

### 本番ビルド用の設定

そのままビルドするとtsファイルの横に大量にjsファイルが作られてしまうため、適当にフォルダを作ってそちらにビルドされるようにします。

tsconfig.json

```json
    "outDir": "./dist"
```

あとは設定ファイルやpackage.jsonも上記フォルダに無いとエラーになってしまうため、スクリプトを作ってコピーするようにしておきます。

copy_statics.js

```javascript
const fs = require("fs-extra");
const targets = ["config", "package.json"];
targets.forEach(path => {
  fs.copySync(path, `dist/${path}`);
});
```

あとはtsファイルのトランスパイル、コピースクリプトの実行を実行し、トランスパイルされたjsファイルを実行するようにビルド＆起動スクリプトを変更します。（startはbuildを実行してからになります）

package.json

```json
    "build": "tsc && node copy_statics.js && nuxt build",
    "start": "cross-env NODE_ENV=production node dist/server/index.js",
```

これで開発環境、本番環境共通のファイルでTypeScriptが動かせるようになっていると思います。

## まとめ

まだ開発中のプロジェクトで試した方法を投稿しただけになりますので、場合によってはうまく動かないパターンなどが出るかもしれません。何か見つかり次第また追記していきます。

とりあえず未定義の変数を使う場合はエラーになったりしますので、typoを減らしたりという効果はすぐ出そうです。