5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

既存のJavaScriptをTypeScript化してwebpackでビルドしてHeroku上でPM2経由でNode.jsサーバーを起動する

Last updated at Posted at 2019-09-12

はじめに

タイトルの長さ :innocent:
とにかく、素のJavaScriptで書かれた個人開発プロジェクトをモダンな環境にして開発効率を上げるぞ! :muscle:

before

  • 一つのファイルに書かれた大きなJavaScriptファイル(app.js
  • node app.jsでNode.jsサーバーを起動
    • Node.jsサーバーがクラッシュしたら手動で再起動

rootにapp.jsが置かれている、実にシンプルな状態 :innocent:

❯ tree -L 2
.
├── app.js
├── node_modules
│   └── ...
├── package-lock.json
└── package.json

after

  • TypeScript(app.ts
  • webpackでビルド(ファイル分割は今後少しずつやっていく)
  • PM2でNode.jsサーバーを起動
    • クラッシュ時自動で再起動

srcにあるtsファイルをwebpackがビルドしてdistに出力してくれます :relaxed:

❯ tree -L 2
.
├── dist
│   ├── bundle.js
│   └── bundle.js.map
├── ecosystem.config.js
├── node_modules
│   └── ...
├── package-lock.json
├── package.json
├── src
│   └── app.ts
├── tsconfig.json
└── webpack.config.js

手順

TypeScriptの導入

typescriptをインストールします。

npm install -D typescript

以下コマンドでtsconfig.jsonを作成し、編集します。

npx tsc --init
tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2019"],
    "downlevelIteration": true,

    "strict": true,
    "noImplicitAny": false,

    "moduleResolution": "node",
    "esModuleInterop": true,

    "resolveJsonModule": true,   /* app.ts内で.jsonファイルを読み込んでたので必要 */
    "sourceMap": true            /* webpackでバンドルした際、エラー箇所が特定しやすいようにsourceMap追加 */
  }
}

設定内容はgfxさんのこの記事を参考にしました :pray:
TypeScript再入門「がんばらないTypeScript」で、JavaScriptを“柔らかい”静的型付き言語に(gfx執筆)

今後開発していく中で様子を見ながら変更していくと思います。

webpackの導入

webpackと周辺パッケージをインストールします。

npm install -D webpack webpack-cli webpack-dev-server ts-loader webpack-node-externals

webpack-node-externalsはwarningの抑制で使っています。

以下コマンドでwebpack.config.jsを作成し、編集します。

npx webpack init
webpack.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    target: 'node',
    devtool: 'inline-source-map',
    entry: {
        bundle: './src/app.ts'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    externals: [
        nodeExternals()
    ],
    resolve: {
        extensions: ['.ts', '.js'],
        modules: [
            "node_modules",
            path.resolve(__dirname, "src")
        ]
    },
    // .tsファイルをts-loader
    module: {
        rules: [
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                loader: 'ts-loader'
            }
        ]
    }
}

いざ、TypeScript化

app.jsをリネームしてsrcに移動します。

mkdir src
mv app.js src/app.ts

毎回webpackコマンドを叩くのは面倒なのでwebpack-dev-serverに動いてもらいます。

npx webpack --mode development --watch

ここでまず大量にエラー出てくるので、自分で型を定義したり、ライブラリの型定義をインストールしたりしてひたすらエラーをつぶしていきます。
(e.g. npm install -D @types/moment

TypeScript化に成功したら、Node.jsサーバーを起動してみます。

node dist/bundle.js

起動に成功したら、Herokuへのデプロイに移りたいところなんですが、今回からnodeコマンドではなくPM2というNode.jsのプロセスマネージャーを使って起動するようにします。

PM2でNode.jsサーバーを起動する

PM2には便利な機能がたくさんあるんですが、今回はサーバークラッシュ時に自動で再起動させることが主な目的なので特に複雑なことはまだしてません。

PM2にHerokuのインテグレーションも載ってます。ありがたい。
PM2 - Heroku Integration

npm install pm2
npx pm2 init

ecosystem.config.jsファイルが出力されるので、編集します。

ecosystem.config.js
module.exports = {
  apps: [{
    name: 'development',         // 開発環境
    script: './dist/bundle.js',
    instances: 1,
    autorestart: true,
    watch: ['dist'],             // distディレクトリの変更を監視して再起動させる
    env: {
      NODE_ENV: 'development'
    },
  },
  {
    name: 'production',          // 本番環境
    script: './dist/bundle.js',
    instances: 1,
    autorestart: true,
    watch: false,                // ファイル変更の監視は不要
    max_memory_restart: '800M', // メモリ800Mで再起動させる
    env: {
      NODE_ENV: 'production'
    }
  }]
};

開発環境と本番環境で設定を変えたかったので、nameで分けてます。
今思えば、そもそも設定ファイルを分けてしまってもいいと思います :ok_woman:

package.jsonの編集

あとは、package.jsonを編集してエイリアスを追加していきます。

package.json
...
  "scripts": {
    "start": "pm2-runtime start ecosystem.config.js --only production",
    "start:dev": "pm2-runtime start ecosystem.config.js --only development",
    "heroku-postbuild": "npm run build",
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development --watch"
  },
...
  • npm startで本番環境のNode.jsサーバーを起動
  • npm run start:devで開発環境のNode.jsサーバーを起動
  • npm run buildで本番環境のwebpackビルド
  • npm run build:devで開発環境のwebpackビルド(ファイル変更監視)

といった感じです。
また、heroku-postbuildはHerokuへのデプロイ時に走るコマンドです。

つまり、Herokuへデプロイすると、

  • まず依存パッケージのインストール(npm install)が実行される
    • Herokuではbuild時にdependenciesdevDependencies両方インストールされる
  • npm run heroku-postbuildが実行される
    • つまりnpm run buildが実行される
    • つまりwebpack --mode productionが実行される
  • npm startが実行される
    • つまりpm2-runtime start ecosystem.config.js --only productionが実行される

という流れでNode.jsサーバーが起動します。

おわりに

import/exportを使ってファイルを分けたり、webpack.config.jsの環境ごとに用意したり、まだまだやりたいことは多いですが、ひとまず以前よりはだいぶ開発効率が上がったのでこれからがんばるぞい! :muscle:

以上です :smile: :wave:

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?