express-generatorの結果をTypeScriptにリファクタリングしてwebpackでビルドする

# TL;DR.
[今回作ったサンプル](https://github.com/Tetsuya-Minase/program-samples/tree/master/express-ts)

# はじめに
最近Expressを使うときが多々ある。
[generator](https://expressjs.com/ja/starter/generator.html)があるから雛形は一瞬で作れるけど、生成されるのはjs。
このままでも良いには良いんだけど、やっぱりTypeScriptで書きたい。
そこで、TSにリファクタリングした上でWebpackでビルドしてみる。

# 準備
express-generatorをインストールしてプロジェクトを作成するとこまでやっとく。
詳細は[公式サイト](https://expressjs.com/ja/starter/generator.html)から。
生成された`package.json`の内容はコピーしておく。
ちなみにフォルダ構成はこんな感じ。

```sh
.
├── node_modules
├── package-lock.json
├── package.json
└── src
     ├── app.js
     ├── bin
     │   └── www.js
     ├── public
     │   ├── images
     │   ├── javascripts
     │   └── stylesheets
     │       └── style.css
     ├── routes
     │   ├── index.js
     │   └── users.js
     └── views
         ├── error.jade
         ├── index.jade
         └── layout.jade
```

# リファクタリング
とりあえず拡張子を全部`.ts`にするとこからリファクタリングスタート。
方針としては、tsで動くようにするのと`var`とかやめるのをメインにする。
※所々jsのままかも知れない...

## app.ts
生成されたファイルの中でサーバの設定を書いてあるファイル。
一応クラス化して似たような初期化はfunctionにまとめた。
error処理周りはそのまま返却するよう修正。
ソースは下記の通り。

```javascript
import * as createError from 'http-errors';
import * as express from 'express';
import * as path from 'path';
import * as cookieParser from 'cookie-parser';
import * as logger from 'morgan';

import { router as indexRouter } from './routes/index';
import { router as usersRouter } from './routes/users';

class App {
  public express: express.Application = express();

  constructor() {
    this.middleWareInit();
    this.routerInit();
    this.errorHandlerInit();
  }

  /**
   * middleware系初期化
   */
  private middleWareInit() {
    // viewEngineは今回使わないのでスルー
    // view engine setup
    // express.set('views', path.join(__dirname, 'views'));
    // express.set('view engine', 'jade');

    this.express.use(logger('dev'));
    this.express.use(express.json());
    this.express.use(express.urlencoded({ extended: false }));
    this.express.use(cookieParser());
    this.express.use(express.static(path.join(__dirname, 'public')));
  }

  /**
   * router初期化
   */
  private routerInit() {
    this.express.use('/index', indexRouter);
    this.express.use('/users', usersRouter);
    this.express.get('*', (req, res) => {
      res.send(`${req.url}にアクセスしたね。`);
    });
  }

  /**
   * error handling系初期化
   */
  private errorHandlerInit() {
    // catch 404 and forward to error handler
    this.express.use(function (req, res, next) {
      // next(createError(404));
      res.send(createError(404));
    });

    // error handler
    this.express.use(function (err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};

      // render the error page
      res.status(err.status || 500);
      res.send('error');
    });
  }
}

// appをexport
export default new App().express;
```

# index.ts & users.ts
生成されたファイルの中でルーティング系の設定、処理を書くところ。
ここはサラッと。ただtsに書き換えただけ。
両方共内容は大して変わらないから下のサンプルは`index.ts`。

```javascript
import * as express from 'express';
const router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('in index');
});

export { router };
```

# www.ts
生成されたファイルの中でサーバの実行ファイル。
こいつを叩くと起動する。
ほぼそのまま。起動するだけだから良いかなと。

```javascript
/**
 * Module dependencies.
 */
import { default as app } from '../app';
import * as debug from 'debug';
import * as http from 'http';

/**
 * Get port from environment and store in Express.
 */
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */
const server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */
function normalizePort(val) {
  const port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */
function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}
```

# Webpackでビルドする
必要なものは`npm install --save ts-loader typescript webpack webpack-cli`で入れとく。
`webpack.config.js`は下記の通り。
各種ファイルを纏めたくは無いので、`entry`を分けておく。
ついでにフォルダ構成をそのままにしたいので、`key`に出力後のファイル名、`value`に元ファイルを記載する。

```javascript
const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
      'bin/www': path.join(__dirname, 'src/bin/www.ts'),
      'routes/index.js': path.join(__dirname, 'src/routes/index.ts'),
      'routes/users.js': path.join(__dirname, 'src/routes/users.ts'),
      'app.js': path.join(__dirname, 'src/app.ts')
  },
  target: 'node',
  node: {
    __dirname: false // ビルド後のフォルダで参照するにはfalseにしておく
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  resolve: {
    modules: [
      'node_modules'
    ],
    extensions: [
      '.ts', '.js'
    ]
  },
  output: {
    // 出力するファイル名(entryのkeyを指定)
    filename: '[name]',
    path: path.join(__dirname, 'dist')
  }
};
```

ビルド結果後のフォルダ構成はこんな感じ。
一部置いてかれているけど、今回は無視する。
必要に応じて別途コピーすれば問題ないと思う。

```sh
.
├── dist
│   ├── app.js
│   ├── bin
│   │   └── www
│   └── routes
│       ├── index.js
│       └── users.js
├── node_modules
├── package-lock.json
├── package.json
└── src
     ├── app.ts
     ├── bin
     │   └── www.ts
     ├── public
     │   ├── images
     │   ├── javascripts
     │   └── stylesheets
     │       └── style.css
     ├── routes
     │   ├── index.ts
     │   └── users.ts
     └── views
         ├── error.jade
         ├── index.jade
         └── layout.jade
```

# まとめ
念願だったtsでExpressを書くことができた。
正直そもそもtsにする必要があるのかわからないけど、個人的に書きやすいので良しとする。
ただ、ファイルが増える度にconfigを書き換えなきゃ行けないので、そのうち何か考えたいです。