webpack4でTypeScript、Socket.IOの環境を作成してみた

**javascriptを趣味で勉強しているものです。間違いありましたら指摘お願いします**

【[自分が作成したコード](https://github.com/okumurakengo/socketio-typescript-sample)】

# 1 インストール

```bash
yarn add socket.io express
yarn add -D typescript @types/socket.io @types/socket.io-client @types/express @types/node webpack webpack-cli ts-loader webpack-node-externals
```

- `socket.io`
    - socket.io本体、Nodeサーバーで動く機能とブラウザで動く機能の両方がインストールされる
- `express`
- `typescript`
- `@types/socket.io`
    - socket.io のNodeサーバーで動く機能の型定義
- `@types/socket.io-client`
    - socket.io のブラウザで動く機能の型定義
- `@types/express`
    - expressの型定義
- `@types/node`
    - nodeのhttpモジュールを使用しているところ用の型定義
- `webpack`
- `webpack-cli`
- `ts-loader`
- `webpack-node-externals`
    - webpackで生成したファイルを、`node bundle.js`と実行する場合に`node_modules`のファイルを一緒にバンドルしている必要はないので、`node_modules`を無視して外部関数として扱うようにバンドルしてくれる

# 2 フォルダ構成

このようなフォルダ構成で作成してみました。

```bash
.
├ dist/ # webpackでバンドルしたファイルを出力(nodeサーバー用)
├ node_modules/
├ package.json
├ public/  # 静的ファイルを置く
│ ├ index.html
│ └ js/ # webpackでバンドルしたファイルを出力(ブラウザ用)
├ src/ # 実際に作業するファイル
│ ├ client/
│ │ └ index.ts
│ └ server/
│   └ index.ts
├ tsconfig.json
├ webpack.config.js
└ yarn.lock
```

![Screen Shot 2019-06-09 at 11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/fa6328d4-25ba-0e53-f3b2-c95dbdb50454.png)

# 3 `webpack.config.js`

```webpack.config.js
const nodeExternals = require("webpack-node-externals");

module.exports = [{
    // ブラウザで動く機能をバンドル
    mode: "development",
    entry: {
        client: "./src/client/index.ts",
    },
    output: {
        filename: "[name].js",
        // expressでpublicフォルダ配下を静的に読む込むように設定するので、そこに出力する
        path: `${__dirname}/public/js`,
    },
    module: {
        rules: [{
            test: /\.ts$/,
            use: "ts-loader",
            exclude: /node_modules/,
        }]
    },
    resolve: {
        extensions: [".ts", ".js"]
    },
}, {
    // Nodeサーバーで動く機能をバンドル
    mode: "development",
    entry: {
        server: "./src/server/index.ts",
    },
    target: "node",
    node: {
        // expressを使うときにはこの設定をしないと失敗します
        // 参考：https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334
        __dirname: false,
        __filename: false,
    },
    externals: [nodeExternals()],
    module: {
        rules: [{
            test: /\.ts$/,
            use: "ts-loader",
            exclude: /node_modules/,
        }]
    },
    resolve: {
        extensions: [".ts", ".js"]
    },
}];
```

`socket.io`はnodeサーバーで動く機能とブラウザで動く機能があるため、設定を分ける必要があります。
設定ファイルを2つにするとかでもいいのですが、今回は↑のようなやり方で2つの設定を書きました。

複数の設定をエクスポートする
参考：https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations

# 4 `tsconfig.json`

```tsconfig.json
{
    "compilerOptions": {
      "outDir": "./dist/",
      "noImplicitAny": true,
      "module": "es6",
      "target": "es5",
      "allowJs": true
    }
}
```

あまり詳しくないので、[こちら](https://webpack.js.org/guides/typescript/)を参考にして、ほぼそのままです(^_^;)

# 5 簡易チャット作成

### 5-1 サーバ作成

```src/server/index.ts
import * as express from "express";
import * as http from "http";
import * as socketio from "socket.io";

const app: express.Express = express();
const server: http.Server = http.createServer(app);
const io: socketio.Server = socketio(server);

app.use(express.static("public"));

io.on("connection", (socket: socketio.Socket) => {
    socket.on("hoge_message", (msg: string) => {
        io.emit("hoge_message", msg);
    });
});

server.listen(3000, () => console.log("listening on *:3000"));
```

expressでサーバを作成し、`public`フォルダ配下を静的ファイルとしてアクセスできるようにします。

### 5-2 ブラウザ側を作成

```public/index.html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Document</title>
<script src="js/client.js" defer></script>
<form><input><button>Send</button></form>
<ul></ul>
```

```src/client/index.ts
import * as io from "socket.io-client";
const socket: SocketIOClient.Socket = io();

const form: HTMLFormElement = document.querySelector("form");
const input: HTMLInputElement = document.querySelector("input");
const ul: HTMLUListElement = document.querySelector("ul");

form.addEventListener("submit", (e: Event) => {
    e.preventDefault();
    socket.emit("hoge_message", input.value);
    input.value = "";
});

socket.on("hoge_message", (msg: string) => {
    ul.insertAdjacentHTML("beforebegin", `<li>${msg}</li>`);
});
```

### 5-3 実行

```bash:bash
yarn webpack #dist/server.jsとpublic/js/client.jsが出力
node dist/server.js #サーバ起動
```

http://localhost:3000/ を開いてsocket.ioがうまく動いていることを確認できました。

![vdWDonVtHX.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/bcbe906b-40e3-c33f-bfed-16aa17deefac.gif)

# 6 ファイル監視

### 6-1 `webpack -w`

`yarn webpack -w`とウォッチモードで実行すれば、ファイルを監視してくれて、ファイルに変更を加えるたびにバンドルし直してくれますね。
しかしそれだけだと、サーバーの方のファイルは変更するたびに、<kbd>Ctrl+C</kbd>でサーバーを切り、もう一度`server.js`を実行しないと変更が反映されません。なので`nodemon`を使ってみます。

### 6-2 `nodemon`を使う

`nodemon`を使えば、サーバー側のファイルを変更するたびに自動でサーバーを再起動してくれます。

```bash
yarn add -D nodemon-webpack-plugin
```

webpack.config.jsに設定を追加します

```js
const NodemonPlugin = require("nodemon-webpack-plugin");

// ..

    plugins: [
        new NodemonPlugin(),
    ],

// ..
```

全体ではこうなりました

```webpack.config.js
const nodeExternals = require("webpack-node-externals");
const NodemonPlugin = require("nodemon-webpack-plugin");

module.exports = [{
    // ブラウザで動く機能をバンドル
    mode: "development",
    entry: {
        client: "./src/client/index.ts",
    },
    output: {
        filename: "[name].js",
        // expressでpublicフォルダ配下を静的に読む込むように設定するので、そこに出力する
        path: `${__dirname}/public/js`,
    },
    module: {
        rules: [{
            test: /\.ts$/,
            use: "ts-loader",
            exclude: /node_modules/,
        }]
    },
    resolve: {
        extensions: [".ts", ".js"]
    },
}, {
    // Nodeサーバーで動く機能をバンドル
    mode: "development",
    entry: {
        server: "./src/server/index.ts",
    },
    target: "node",
    node: {
        // expressを使うときにはこの設定をしないと失敗します
        // 参考：https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334
        __dirname: false,
        __filename: false,
    },
    externals: [nodeExternals()],
    module: {
        rules: [{
            test: /\.ts$/,
            use: "ts-loader",
            exclude: /node_modules/,
        }]
    },
    plugins: [
        new NodemonPlugin(),
    ],
    resolve: {
        extensions: [".ts", ".js"]
    },
}];
```

この状態で`yarn webpack -w`とします。(※`nodemon-webpack-plugin`はウォッチモードの時のみ動作してくれます)

![91w2XiusLF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/519145b5-35b0-a688-2d62-7b55f7c63208.gif)

`yarn webpack -w`を実行すると自動でサーバーが起動し、その後ファイルを変更するとサーバーが再起動され、変更が反映されていることを確認できました。

---

最後まで読んでいただいてありがとうございました。m(_ _)m