2018-09-19に更新

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のプロジェクト作成で良さそうなのは?

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

Nuxt.js側をTypeScript化する

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

nuxt-community/typescript-template: Typescript starter with Nuxt.js

TypeScript関連をインストール

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

yarn add --dev typescript [email protected]

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

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

yarn add nuxt-property-decorator vue-property-decorator

設定ファイルを準備

先程の typescript-template のリポジトリから、下記のファイルをそのままコピーしてきます。

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

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

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

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

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

<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します。

export default class extends Vue {

Componentデコレータを使う

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

@Component({
  components: {
    SubComponent
  }
})

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

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

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でサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。

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

yarn add --dev ts-node

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

nodemon.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等)が定義されているとエラーになってしまうようなので、クラス化したり変数名を変えるか、下記のようにファイル全体を中括弧で囲んでおくかします。

{
  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等をつけておいてください。

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

本番ビルド用の設定

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

tsconfig.json

    "outDir": "./dist"

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

copy_statics.js

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

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

package.json

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

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

importがエラーになる場合

開発サーバー実行時にサーバー側のみに限りimportを行うとエラーになると思いますので、その場合はnodemon.jsonのスクリプトを下記の様に変更するとimportできるようになります。

    "exec": "ts-node -O '{\"module\": \"commonjs\"}' ./server/index.ts"

本番もざっと検証してみましたが、やはりtsconfigの方もcommonjsでないと動きませんでした。あと、postcss関連のエラーが出るようなのでnuxt-edgeに入れ替える必要があります。

参考 【実録】Nuxt.jsの既存プロジェクトを一足早くNuxt v2( nuxt-edge )へとアップグレードする方法

まとめ

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

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


dala00

Crieitの開発者です。 主にLAMPで開発しているWebエンジニアです(在宅)。大体10年程。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel5, CakePHP3, JavaScript, RoR 趣味:Elixir, Phoenix, Node, Nuxt, Express, Vue等色々

Crieitはαバージョンで開発中です。進捗は公式Twitterアカウントをフォローして確認してください。 興味がある方は是非記事の投稿もお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか
関連記事

コメント