2018-11-28に更新

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.js1の情報が混じっていますので、下記も合わせて確認が必要な箇所があります。

Nuxt.jsのプロジェクトをNuxt.js2にバージョンアップしてみた

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.js2の場合は4)。無指定だとこのプロジェクトに合わないバージョンが入ってエラーになってしまいます。

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

yarn add nuxt-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, Prop } from "nuxt-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",

あと、本番ビルドのフォルダの関係でrequireやimportで.tsのように拡張子をつけている場合はエラーになるため、拡張子は削除しておきましょう。Sequelizeの models/index.ts の中身も変更しておきます。

before

    return (
      file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".ts"
    );

after

    const ext = file.slice(-3);
    const validExt = ext === ".ts" || ext === ".js";
    return file.indexOf(".") !== 0 && file !== basename && validExt;

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

importがエラーになる場合

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

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

本番もざっと検証してみましたが、やはりtsconfigの方もcommonjsでないと動きませんでした。あと、postcss関連のエラーが出るようなのでNuxt1の場合はNuxt2にアップグレードが必要です。

参考
Nuxt.jsのプロジェクトをNuxt.js2にバージョンアップしてみた

まとめ

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

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

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント