JavaScript
Express
TypeScript
Docker

TypeScriptでExpress.js開発するときにやることまとめ (docker/lint/format/tsのまま実行/autoreload)


やること

ステップごとに分けてあるので、導入したい箇所だけつまみ食いすることも可能です。

気力が残っていれば、追記で本番環境の運用についても書きたいです…


  1. express.jsをDockerで起動

  2. TypeScript導入

  3. TSLint + Prettier導入

  4. node-ts導入: dev環境はトランスパイルなしでtsファイルのまま開発

  5. nodemon導入: dev環境はファイル変更を検知して自動リロード

記事ではnode.js 8.12を使用しています。

8系から、npm install時に自動的に--saveオプションが付くようになっていますので、

記事内で「あれ?--saveしてなくない?」とならないように一言補足。


まずは最小構成でexpress.js起動


準備

(追記)

エディタ補完を効かせるには結局MacにNode.js入れないといけなかったりしましたが、

VSCodeがDockerコンテナのリモート開発に対応したりして潮目も変わってきたので

過激派用の設定も追記しました。

Dockerで立ち上げた開発環境をVS Codeで開く!


A. MacにNode.js入れている場合

PJフォルダ(仮にts-express)を作成し、とりあえずMac環境でinit

npm init -y


B. MacにNode.js入れない硬派な場合

PJフォルダ(仮にts-express)を作成し、空のjsonを作成

(何はともあれpackage.jsonがないとDockerイメージのビルドに失敗するので)

echo "{}" >> package.json


Docker開発環境整備

Dockerfile 作成

dockerのなんやかんやは素晴らしい記事がたくさんあるので、簡潔な説明に留めます…


Dockerfile

# ベースイメージはお好みで普通のでもalpineでも。

FROM node:8.12.0-slim
ENV APP_ROOT /app/

WORKDIR $APP_ROOT

# package.jsonとpackage-lock.jsonを先にコピー。
# package*.jsonだけを先に個別コピーすることで、パッケージ変更時は`RUN npm install`が走るが
# それ以外のファイル変更時は同コマンドにはキャッシュ利用で飛ばされるため、ビルド時間を短縮できる。
COPY package*.json $APP_ROOT
RUN npm install

# その他ファイルをコピー。
COPY . $APP_ROOT


docker-compose.yml 作成

dockerコマンド叩くときに毎回引数つけるのが面倒なので、docker-compose作ります。


docker-compose.yml

version: '2.1'

services:
web:
build:
context: .
dockerfile: ./Dockerfile
command: npm run dev
environment:
NODE_ENV: development
ports:
- '3000:3000'
volumes:
- .:/app

入る

docker-compose run --rm web bash

以降、別段コメントがなければbashコマンドはdocker内で実行します。

Mac側でnpm initしていない場合はここでやる

npm init

expressインストール

npm i express

テスト用src/index.js作成


src/index.js

const express = require('express');

const app = express();
const port = 3000;

app.get('/', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));


package.jsonにdev実行コマンドを追加


package.json

"scripts" {

"dev": "node src/index.js"
}

起動テスト

# Macの別窓terminalで実行

docker-compose up --build web

ブラウザでhttp://localhost:3000/を開くと Hello World! が出ます :tada:

出たら一度Ctrl+cで止めましょう。

(追記)

docker-compose upは、2回目以降は実は毎回ビルドしなくても良かったりします。

docker-compose.ymlファイルのvolumesに書いてある通りファイルの変更は

dockerの中でいじろうがホスト側でいじろうが常に双方向に更新されます。

なので、キャッシュが効いて高速にビルドされるとはいえ、数秒の待ちが煩わしい人は

普段は--buildなし、何か挙動がおかしくなったら--build付けて再ビルドが

お手軽かなと思います。堅実に生きたい人は常に--buildで。


TypeScript導入

TypeScriptと型情報を追加

npm i --save-dev typescript @types/express

src/index.jssrc/index.tsに名前変更

とりあえずトランスパイル出来ることだけを確認したいので、中身は変更しない。

/distディレクトリを作成

mkdir dist

tsconfig.json を作成

requireの記法を使うなら "module": "commonjs"

importの記法を使うなら "module": "es6" (TSなら普通こっちですかね。)


tsconfig.json

{

"compilerOptions": {
"outDir": "./dist",
"sourceMap": true,
"module": "es6",
"target": "es5",
"moduleResolution": "node",
"removeComments": true
},
"include": [
"./src/**/*"
]
}

モジュールのインポートがcommonjsの場合はsrc/index.tsの1行目を書き換え

// const express = require('express'); // これを

import * as express from "express"; // こう

package.jsonのscriptsを編集


package.json

"scripts": {

// distから走るように修正。
"dev": "node dist/index.js",
// 追加。TypeScriptをグローバルではなくローカルインストールしているため、npm run tscでコンパイルがちゃんと走るようにする
"tsc": "tsc"
}

トランスパイルする

npm run tsc

これでdistにトランスパイル後のファイルが出来上がるので、改めて走らせてみる

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。

docker-compose up --build web


TSLint + Prettier導入(入れたい方のみ)

(ESLintの場合はこのブログ記事が参考になります。 ESLint(あるいはTSLint)とPrettierを併用する

リンターとフォーマッターの導入です。

両方入れる方法はいくつかあるのですが、ここではTSLintにPrettierの処理もまかせる方式で行きます。

この方式だと、設定ファイルがtslint.json一箇所にまとまって見通しが良くなります。

npm i -D tslint prettier tslint-config-prettier tslint-plugin-prettier

〜簡潔に説明〜



  • tslint リンター本体


  • prettier フォーマッター本体


  • tslint-config-prettier TSLintとPrettierの重複処理を無効化


  • tslint-plugin-prettier ESLintのルールとしてPrettierを読み込む

(optional)ベースルールを使用したい人は導入します。僕の場合はeslintのstandardが好きなので、

そのルールをTSに合うようにカスタマイズされたtslint-config-standardを使用します。

npm i -D tslint-config-standard

tslint.jsonファイルを作成し、設定を書きます。

extendsのtslint-config-standardは上記で導入したルールに読み替えてください。

入れなかった場合はtslint-config-prettierだけ記述すればOKです。


tslint.json

{

"rulesDirectory": ["tslint-plugin-prettier"],
"extends": ["tslint-config-standard", "tslint-config-prettier"],
"rules": {
// TSLintに任せる方法の場合、prettier設定もここに書くことになります。
// 設定内容はstandardだとsingleQuoteとsemiだけ気をつければ多分大丈夫です。
// また、完全に僕の好みですが行あたり80文字は短すぎるので長めにしてあります。
"prettier": [
true, {
"singleQuote": true,
"semi": false,
"printWidth": 120 // defaultの80って短くないですか?
}
]
}
}

上記を動かせるように、lintコマンドを追加します。


package.json

"script" {

"lint": "tslint --exclude **/*.d.ts --project . --fix 'src/**/*.ts' 'test/**/*.ts'"
}

--project .は付けないと、rule requires type informationの警告が大量に出るため付けています。

テストディレクトリを含めるかどうかはお好みで。

またlintコマンドのオプションは色々な指定方法があるので、PJに合わせて適宜変更してください。

では、作成したlintコマンドテストをテスト。

npm run lint

自動修正できないものは無理ですが、修正できるものはこれで一発修正されます。

例えば、standardルールなら、最初に作ったsrc/index.tsの;が全部吹っ飛んだはずです。

(optional)VSCodeの人

TSLintプラグインを導入し、設定で "tslint.autoFixOnSave": true にすれば

保存のたびにLint+Prettierしてくれます。


ts-nodeを使ってトランスパイルなしで開発

ここまでくればTSでコードを書いていけるようになりましたが、コードを変更するたびに

npm run tscしてdocker-compose up webし直さなければなりません。それは流石にキチィ。

というわけで、ts-nodeを入れます。こいつを入れることで、nodeコマンドでjsファイルを

実行するのと同じようにts-nodeでtsファイルを実行できるようになります。

インストール

npm i -D ts-node

設定

npm run devコマンドでnodeではなくts-nodeを使用し、srcのtsファイルを直接読むようにします。

また、本番環境ではトランスパイル後の生jsを走らせると思うのでdistからの実行コマンドを別で用意します。

ts-nodeは例によってローカルインストールのため、パスが通るように手動でscriptに追加します。

追記: ts-nodeの本番環境運用も試しており、結論から言うと起動時間が若干長いという以外の

オーバーヘッドは殆ど無いので、本番実行も ts-node ./src/index.ts でも良いと思います。


package.json

"scripts": {

"start": "node dist/index.js", // 本番実行用のコマンド。これの前にtscも走らせること。
"dev": "ts-node ./src/index.ts", // 開発環境用のコマンド。
"ts-node": "ts-node"
}

テスト

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。

docker-compose up --build web

トランスパイルしなくても、直接tsファイルを読み出すようになりました。


変更検知して自動リロード

tsファイルを直読めるようになったとはいえ、まだこのままではファイルに変更が加わるたびにサーバーを止めて

'npm start'し直さなければなりません。それは流石にキチィ。

というわけで、nodemonを入れます。こいつを入れることでコードの変更を検知して

サーバーをオートリロード出来るようになります。

インストール

npm i -D nodemon

設定ファイルnodemon.jsonを新規作成


nodemon.json

{

"watch": ["src"],
"ext": "ts",
// テストファイル等、リロード対象にしたくない物があればここに追加
"ignore": ["tests/**/*.ts"],
// 実際に実行するコマンド。先程のts-nodeのコマンドです。
"exec": "npm run ts-node ./src/index.ts"
}

devコマンドでnodemonが起動するように修正


package.json

"scripts": {

"dev": "nodemon -L"
}

テスト

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。

docker-compose up --build web

試しにsrc/index.jsのconsole.logのコメントをちょっといじって保存してみて下さい。

docker-composeの実行画面にリロード状況が出て、リロード後ブラウザでlocalhost:3000を

開き直すと変更が適用されているのが分かると思います。


おわり

これで、快適なTypeScript + Express.js開発環境を手に入れることができました。

Dockerfile, docker-compose.yml, その他lint, prettier等の各種設定は

プロジェクトにより色々変わってくると思うので、この記事はあくまでベースとして使用して

修正加えてください。