TypeScript+Webpack+Node(Express)でファイル変更後にnodeを自動再起動する

## はじめに
この記事は[Linkbal Advent Calendar 2018](https://qiita.com/advent-calendar/2018/linkbal)の3日目の記事です。昨日は弊社CTOの[開発組織の育て方 – エンジニア3人から30人へ](https://tech.linkbal.co.jp/4993/)という、組織と事業が拡大していくことによる技術や採用の変遷のお話です。ぜひご一読いただければ幸いです。

リンクバルに新卒入社して2年目の水野（キンチキ）です。今年の10月からエンジニアではなくビジネス側(？)になったのですが、プライベートでも仕事でもコードはちょくちょく書いているので、もちろんアドベントカレンダーも書きます！

今回はTypeScript+Webpack+Node(Express)でNodeの再起動を自動で行う方法がわからず、あまり良いとは思わないながらも解決できた方法について書きます。

ちなみにリンクバルはTyepScriptを採用していない(！)です。学習コストは仕方ないとしても、型があるおかげでバグは間違いなく減ると思います。あと採用面での効果は絶対に＋のはず（水野予測）。

いずれ使ってくれるといいなあ。


## パッケージのインストール
最初の工程から説明します。

必要なパッケージを以下のコマンドでインストールします。
`yarn add`のオプションの`-D`は`--dev`と同じです。
lintは省いています。

```sh
$ mkdir sample && cd $_
$ yarn init -y # 初期化
$ yarn add -D @types/express typescript ts-loader webpack webpack-cli nodemon webpack-node-externals # パッケージインストール(devDependencies)
$ yarn add express # パッケージインストール(dependencies)

# npmを使う場合
$ npm init -y
$ npm i -D ...
$ npm i -S express
```

expresだけdependenciesにしていますが、devDependenciesでもいいかもしれません。
私はdependenciesにしないといけないパッケージがよくわかっていないです。。。

インストールしたパッケージの用途です。

- @types/express: express用の型定義
- typescript: TypeScript
- ts-loader: webpackがTypeScriptをコンパイルできるようになるやつ
- webpack, webpack-cli: webpack周り
- nodemon: node再起動
- webpack-node-externals: WARNING抑制（必須ではない）


## tsconfigの作成
tscでtypescriptの設定ファイル（tsconfig）を作成します。

```sh
$ npx tsc --init
```

tsconfigを以下のようにします。
これは開発用なので、本番環境用を作成する際は別途ファイル作成するなどして内容を適宜変更してください。

```js:./tsconfig.js
{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "lib": [
      "es2018",
      "dom"
    ],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false
  }
}
```

## webpack.configの作成
`touch webpack.config`やエディタでファイルを作成し、以下のようにしてください。

エントリーポイントは`./src/server.ts`とし、最終的にビルドされるファイルは`dist/server.js`としています。
`watch`をtrueにしておくことでファイルの変更を検知します。

こちらも開発用なので、本番環境用を作成する際は別途ファイル作成するなどして内容を適宜変更してください。

```js:./webpack.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  watch: true,
  mode: 'development',
  entry: './src/server.ts',
  target: 'node',
  externals: [nodeExternals()],
  devtool: 'inline-source-map',
  module: {
    rules: [
      {
        loader: 'ts-loader',
        test: /\.ts$/,
        exclude: [
          /node_modules/
        ],
        options: {
          configFile: 'tsconfig.json'
        }
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js']
  },
  output: {
    filename: 'server.js',
    path: path.resolve(__dirname, 'dist')
  }
};
```

## nodemon.jsonの作成
`touch nodemon.json`などで作成し、以下のようにしてください。

watch対象をwebpackのoutputに指定しているディレクトリ（この場合`dist`）にするのがポイントです。こうすることで、コンパイル後に自動でnodeを再起動してくれます。

```json:./nodemon.json
{
  "restartable": "rs",
  "watch": [
    "dist"
  ],
  "env": {
    "NODE_ENV": "development"
  }
}
```

## expressの実装
`src`ディレクトリを作り、そこに`server.ts`を作成します。`/`へのgetリクエストに文字列を返す処理だけ書いておきましょう。

```sh
$ mkdir src
$ touch src/server.ts
```

```ts:src/server.ts
import * as express from 'express';

const app: express.Application = express();
const router: express.Router = express.Router();
const port = process.env.PORT || 3000;

router.get('/', (req: express.Request, res: express.Response) => {
  res.send('Hello, Express with TypeScript!');
});
app.use('/', router);

app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}/`);
});
```


最後にpackage.jsonにscriptsを追加しましょう。
webpackとnodemonを`&`で繋いで同時起動しています。tsファイルが変更されたらwebpackがコンパイルし、nodemonがnodeを再起動します。

```json:./package.json
{
  "name": "express-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon dist/server.js & webpack --config webpack.config.js"
  },
  "devDependencies": {
  "以下略"
}
```

ここまできたら `yarn run dev` で動かすことができます。

![auto-restart-node.gif](https://qiita-image-store.s3.amazonaws.com/0/130946/7c901d1c-b5c8-2083-7a4b-ff829ed32fb2.gif)

ファイルを編集したらnodeが再起動し、リロードしたら反映されていますね！

## おわりに
この方法だとエラー箇所がわかりづらかったり、webpackがクラッシュしたらプロセスが残ったりしますが、あまりいい方法が見つからなかったのでいったんこれで開発しています。この方法は手軽なのがウリです。webpack-dev-serverでうまいことホットリロードできると思いましたが、やり方がわかりませんでした。この方法でも支障は特にありません。

プロセスが残っていたら以下のコマンドで一括killしましょう。

```sh
kill -9 `ps -ef | grep nodemon |  awk '{print $2;}'`
```

リンクバルでTypeScriptが採用される日を待っています（強い意思はない）。

## 参考
- [Get started Express + TypeScript する - Qiita](https://qiita.com/IgnorantCoder/items/c9b79dbab8c1a34b769f)

このページも結構参考にしたのですが、今は見れなくなっています。
https://dev.to/briandgls/using-typescript-with-express--e0k

ts-nodeを使ったりすればホットリロードできそうですね。

- [【ホットリロード】nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。 - aknow2](https://aknow2.hatenablog.com/entry/2018/04/09/150006)
- [Nuxt.js+ExpressのプロジェクトをTypeScript化する - Crieit](https://crieit.net/posts/Nuxt-js-Express-TypeScript)
- [Nuxt+Expressのプロジェクト作成で良さそうなのは？ - Crieit](https://crieit.net/posts/Nuxt-Express)