水無瀬のプログラミング日記

プログラムの勉強した時のメモ

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

TL;DR.

コード

初めに

最近Expressを使うときが多々ある。
generatorがあるから雛形は一瞬で作れる。
当たり前だけど生成されるのはjs。
このままでも良いには良いんだけどTypeScriptで書きたい。
そこで、TSにリファクタリングした上でWebpackでビルドしてみる。
webpackなのはtscでビルドするのがめんどくさいから。

準備

express-generatorをインストールしてプロジェクトを作成するとこまでやっとく。
上のサイトから見ればすぐ。
生成されたpackage.jsonのinstallされているものはコピーしておく。

リファクタリング

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

app.ts

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

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

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

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

/**
 * 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は下記の通り。
実行するwww.tsをentryにしておく。
出力はまとまっているのでserver.jsという名前にした。

const path = require('path');

module.exports = {
  mode: 'development',
  entry: './tsserver/bin/www.ts',
  target: 'node',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  resolve: {
    modules: [
      path.resolve(__dirname, "tsserver"),
      'node_modules'
    ],
    extensions: [
      '.ts', '.js'
    ]
  },
  output: {
    // 出力するファイル名
    filename: 'server.js',
    path: path.join(__dirname, 'dist')
  }
};

実行 & 確認してみる

事前にコピったpackage.jsonの内容をexpressコマンドを叩いたフォルダのpackage.jsonに戻してインストール。
その後、node server.jsで実行。
結果は画像の通り。

f:id:minase_mira:20181003225713p:plainf:id:minase_mira:20181003225723p:plainf:id:minase_mira:20181003225811p:plain

まとめ

念願だったtsでExpressを書くことができた。
正直そもそもtsにする必要があるのか。
webpackでまとめる必要があるのかとかとかあるけど、気にしない方向で。

おまけ

静的ファイル返したい

res.sendFile('ファイル')でOK。
今回の例だとしたみたいな感じ。

// app.tsのrouterInit内
this.express.use('/index', indexRouter);
this.express.use('/users', usersRouter);
this.express.get('*', (req, res) => {
    res.sendFile(path.join(path.resolve(''), './dist/index.html'));
    // res.send(`${req.url}にアクセスしたね。`);
});

フォルダ構成変えないでビルドする

できたのでまとめた。