かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript で Express 開発環境を作るぞぃ!

ほぼコレを参考にしています

構成
/app
  |- /dist # build されたアプリの出力先
  |- /src
  |    |- index.ts # express entrypoint
  |- /webpack
  |    |- base.config.js # 共通設定
  |    |- dev.config.js
  |    |- prod.config.js
  |- nodemon.json
  |- package.json
  |- tsconfig.json
  • 開発中は nodemon で ts-node を実行して src 内の TypeScript を監視して開発
  • development / production それぞれのモードで build して確認できるようにする

開発環境 Express + TypeScript を nodemon で監視しながら動作させる

$ npm install --save-dev @types/express typescript ts-node nodemon
$ npm install --save express
TypeScript の変換方法の設定

tsconfig.json

{
  "compilerOptions": {
    "strict": true,
    "module": "commonjs",
    "target": "es2017",
    "sourceMap": true,
    // import * as _ from '_' はエラー、代わりにimport _ = require('_') を使う
    "esModuleInterop": true,
    // 暗黙的にanyになる値をエラーにする
    "noImplicitAny": true,
    // tscのモジュール解決の方法
    "moduleResolution": "node",
    // コメントを削除する /*! から始まるコメントは残る
    "removeComments": true,
    // import時にファイルパスの文字列で大文字小文字を区別するかどうか
    "forceConsistentCasingInFileNames": true,
    // 以前コンパイルを実行したコードと現在のコードとの差分を検出して、必要なファイルだけをコンパイルする
    "incremental": false,
    // コンパイルした js ファイルの出力先
    "outDir": "dist",
    // 相対的なカレントディレクトリをどこにするかの指定値、./の場合はtsconfig.jsonが置いてあるディレクトリ
    "baseUrl": "src",
    "paths": {
      "*": [
        "node_modules/*",
        "src/types/*"
      ]
    },
  },
  // コンパイルする対象ファイルの場所
  "include": [
    "src/**/*"
  ]
}
nodemon の設定

nodemon.json

{
  "restartable": "rs",
  "env": {
    "NODE_ENV": "dev"
  },
  "watch": [
    "src"
  ],
  "ext": "js, ts, json",
  "ignore": [
    "tests/**/*.ts"
  ],
  "exec": "ts-node ./src/index.ts"
}

TypeScript を監視対象にするために ext オプションを使います。
cf. Node.js + TypeScript nodemon + ts-node で変更が watch されず ホットリロードされないにハマる - かもメモ

Express

localhost:3000 にアクセスしたら hello world するだけのアプリ

/src/index.ts

import express from 'express';
const PORT = 3000;
const app = express();
const router = express.Router();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

router.get('/',
  async (req: express.Request, res: express.Response): Promise<void> => {
    res.send('hello world');
  },
);
app.use('/', router);

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

export default app;
実行スクリプトの作成

package.jsonスクリプトを追加

"scripts": {
  "dev": "nodemon -L"
}

$ npm run dev を実行して localhost:3000 にアクセス。 「hello world」が表示されていて、/src/index.ts を変更したら変更がホットリロードで反映されるようになっていれば OK

Webpack での build 環境の作成

パッケージのインストール

$ npm install --save-dev webpack webpack-cli webpack-node-externals webpack-merge ts-loader
  • webpack-node-externals … node_modules をバンドルさせないためのプラグイン
  • webpack-merge … webpack の設定をマージするためのプラグイン

webpack.config の作成

development, production 共通で使用する設定

/webpack/base.config.js

const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const path = require('path');
const BUILD_ROOT = path.join(__dirname, '../dist');
const SRC_ROOT = path.join(__dirname, '../src');

module.exports = {
  context: SRC_ROOT,
  entry: path.resolve('src', 'index.ts'),
  externals: [nodeExternals()],
  output: {
    filename: 'server.js',
    path: BUILD_ROOT,
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
        options: {
          configFile: 'tsconfig.json',
        },
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js', '.json'],
  }
};
development mode

/webpack/dev.config.js/

const baseConfig = require('./base.config.js');
const { merge } = require('webpack-merge');

const config = merge(baseConfig, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: 'dist',
    host: '0.0.0.0',
    port: 3000,
  },
});
module.exports = config;
production mode

/webpack/prod.config.js

const baseConfig = require('./base.config.js');
const { merge } = require('webpack-merge');

const config = merge(baseConfig, {
  mode: 'production',
});
module.exports = config;

build 用の npm-script を作成

package.json

"scripts": {
  "dev": "nodemon -L",
  "build:dev": "NODE_ENV=dev webpack --config ./webpack/dev.config.js",
  "build": "NODE_ENV=prod webpack --config ./webpack/prod.config.js",
  "start:dev": "npm run build:dev && node dist/server.js",
  "start": "npm run build && node dist/server.js"
}

$ npm run start:dev, $ npm run start をそれぞれ実行して build, localhost:3000 でアプリが表示されていればOK

ESLint, Prettier

お好みで。

パッケージのインストール

npm isntall --save-dev eslint eslint-config-prettier eslint-plugin-prettier \
@typescript-eslint/eslint-plugin @typescript-eslint/parser prettier
ESLint の設定

.eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint',
  ],
  rules: {
    'newline-before-return': 'error',
    'no-console': 'warn',
    'no-var': 'error',
  },
};
Prettier の設定

.prettierrc

{
  "bracketSpacing": true,
  "printWidth": 80,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "useTabs": false,
}

VS Code ファイル保存時に自動的に ESLint, Prettier を実行させる

$ mkdir .vscode
$ touch .vscode/settings.json

.vscode/settings.json

{
  "eslint.enable": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "files.exclude": {
    "**/node_modules": true
  },
  "search.exclude": {
    "**/node_modules": true,
  }
}

JavaScript / TypeScript ファイルに ESLint のエラーの表示・保存時に自動整形されるようになっていればOK

所感

快適 Express 開発環境ができました!
Webpack でコンパイルしているので開発・本番環境の切替も楽そうです。 GitHub にテンプレートとして作っておくと便利そう… (あとでやる)

Next Step Express を AWS Lambda にデプロイする serverless に挑戦していきたいです!


[参考]

新世界交響楽

新世界交響楽