はじめに
タイトルの長さ
とにかく、素のJavaScriptで書かれた個人開発プロジェクトをモダンな環境にして開発効率を上げるぞ!
素のJavaScriptで書かれたnode.jsコードをTypeScript化&webpack導入でDX向上させて、PM2導入でクラッシュ時/メモリ上限再起動も対応して、どんどん強くしてるぞ、、、たのしい!!!わからんこと多いけど新しいことにチャレンジできて嬉しい☺️
— にっしー👨💻岡山⇄東京 (@paranishian) August 31, 2019
before
- 一つのファイルに書かれた大きなJavaScriptファイル(
app.js
) -
node app.js
でNode.jsサーバーを起動- Node.jsサーバーがクラッシュしたら手動で再起動
rootにapp.js
が置かれている、実にシンプルな状態
❯ tree -L 2
.
├── app.js
├── node_modules
│ └── ...
├── package-lock.json
└── package.json
after
- TypeScript(
app.ts
) - webpackでビルド(ファイル分割は今後少しずつやっていく)
- PM2でNode.jsサーバーを起動
- クラッシュ時自動で再起動
src
にあるtsファイルをwebpack
がビルドしてdist
に出力してくれます
❯ 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
{
"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さんのこの記事を参考にしました
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
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
ファイルが出力されるので、編集します。
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
で分けてます。
今思えば、そもそも設定ファイルを分けてしまってもいいと思います
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時に
dependencies
とdevDependencies
両方インストールされる
- Herokuではbuild時に
-
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
の環境ごとに用意したり、まだまだやりたいことは多いですが、ひとまず以前よりはだいぶ開発効率が上がったのでこれからがんばるぞい!
以上です