Nestjs (Expressjs) でWebpack を使用する

#本記事について
　ある時、[Nestjs](https://docs.nestjs.com/) を利用して開発するにあたり、『[webpack](https://webpack.js.org) を使用したらそのまま開発用サーバーが起動するようになったら便利だなぁ』と思った。
　...まではいいけど、実際に構築開始してみると、サーバー/クライアントそれぞれにwebpack を適用するとなると、自分がまだ無知だからだろうか、想像以上に時間を要したので、その結果をこの記事にまとめることにした。

# 目標
　① `$ webpack` コマンドを使用したら、そのままコンパイルして開発用サーバを建ててくれる
　② そのサーバを建てた流れで、client側 も `webpack` で bundling してくれ、サーバは、その bundling された出力ファイルを提供する。


#Nestjs とは
　__Nestjs__ とは、[expressjs](https://expressjs.com/)・[Fastify](https://github.com/fastify/fastify)　のサーバ用Nodejsフレームワークを、別の形として提供するもの。

　TypeScript (公式サイトによると普通のJavaScriptでも可能らしい） を推していて、`OOP（オブジェクト指向プログラミング）`・`FP（関数型プログラミング）`・`FRP（関数型リアクティブプログラミング）`のいいとこ取りをしたもの。
（公式を読めば分かるが、`OOP` に関しては、`DI`とか駆使してて分かるけど、`FP/FRP` に関してはどの辺が該当するのかわからなかった。てかそもそも`FP/FRP`への理解度が浅い。）

# ディレクトリ構成
server側には、Nestjs（Express）
client側には、Reactjs を使用している

```
┣ client
┃  ┣ index.html
┃  ┣ main.tsx
┃  ┗ 〜 other miscellaneous directories or files for Reactjs 〜
┣ server
┃  ┣ main.ts
┃  ┣ root
┃  ┃  ┣ root.module.ts
┃  ┃  ┗ 〜 other files for root module 〜
┃  ┗ 〜 other miscellaneous directories 〜
┗ webpack
   ┣ client 
   ┃  ┣ webpack.common.js
   ┃  ┣ webpack.dev.js
   ┃  ┗ webpack.prod.js
   ┗ server
      ┣ webpack.common.js
      ┣ webpack.dev.js
      ┗ webpack.prod.js
```



# webpack について詳しくない人向け

https://www.udemy.com/course/front-env-setting-webpack/

　こちらのudemy の講座が（たしか）無料で、webpack を学ぶにはいい講座でした。
　この講座と、[公式サイト](https://webpack.js.org) を見比べながら進めれば大体理解できた。

　以下から、本題に入っていく。
#目標①
###nodemon-webpack-plugin を使用する

#### nodemonとは
・node を使用したアプリケーションに適用でき、ファイルを監視して、変更があったら自動で変更を適用してくれる。
・参考(npm): https://www.npmjs.com/package/nodemon

#### webpack への適用
　この __nodemon__ を適用できる`webpack`の拡張機能が、`nodemon-webpack-plugin`として提供されているので、それを使用。

### インストール & コーディング
```
$ yarn add --dev nodemon-webpack-plugin
```

```.js:webpack/server/webpack.dev.js
const webpackMerge = require('webpack-merge');
const NodemonPlugin = require('nodemon-webpack-plugin')
const commonConf = require('./webpack.common');
const path = require('path')

module.exports = webpackMerge(commonConf, {
  mode: 'development',
  watch: true,
  plugins: [
    new NodemonPlugin({
      watch: path.join(process.cwd(), './dist'),
      script: path.join(process.cwd(), './dist/server.js')
    })
  ]
})
```

__watch__ オプションは、変更を監視する対象のディレクトリ
__script__ オプションは、実行するファイル

### webpack-node-externals の使用
####なんで必要か
  https://www.npmjs.com/package/webpack-node-externals#why-is-not-bundling-node_modules-a-good-thing

　↑ このサイトを見てもらえれば分かるのだが、Webpack でそのままサーバを立てるとき（厳密には違うかもしれないけど）、node_modules を`webpack`での bundling 対象外にしないといけないらしい。

　曰く、
> 要約すると、アプリで使用されるパッケージは全てnpm によって管理される方が便利

　とのことらしい。なんかよくわからないけど、解釈としては`パッケージ群はnpmで既に管理してるんだからwebpackは余計なことすんな` てこと？


#### インストール & コーディング
```
$ yarn add --dev webpack-node-externals
```

```.js:server/webpack.common.js
const path = require('path');
const nodeExternals = require('webpack-node-externals')

module.exports = {
  entry: path.resolve(process.cwd(), './server/main'),
  target: 'node',
  externals: [nodeExternals()],
  output: {
    path: path.resolve(process.cwd(), './dist'),
    filename: 'server.js',
  },
  ...
  ...
}
```

うーん、こっちは特筆すべきことはないかなぁ。。。

　（nodeExternalsに関係ないけど）注意点としては、`nodemon-webpack-plugins` の script オプションによる実行ファイルと`webpack` の output オプション の出力ファイル名を一致させておくこと。

#目標②
### webpack-dev-middleware の使用

####webpack-dev-middleware とは
　expressjs の開発用サーバに使用でき、クライアント側のファイルをまとめた（bundleした）ものをサーバ側に提供してくれる。

####インストール & コーディング

```
$ yarn add --dev webpack-dev-middleware
```

```.ts:server/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import * as webpack from 'webpack';
import * as clientWebpackConfig from '../webpack/client/webpack.dev';
const webpackDevMiddleware = require('webpack-dev-middleware');

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(RootModule);
  const compiler = webpack(clientWebpackConfig())
  app.use(webpackDevMiddleware(compiler))
  await app.listen(3000);
}
bootstrap();
```

<details><summary>`webpack/client/webpack.*`ファイルの詳細</summary>
<div>

```.js:webpack/client/webpack.common.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = ({ outputFilename }) => ({
  entry: './client/main',
  output: {
    path: path.resolve(process.cwd(), 'dist/client'),
    filename: `${outputFilename}.js`,
    chunkFilename: `${outputFilename}.js`,
  },
  resolve: {
    alias: {
      '@': path.resolve(process.cwd(), './'),
    },
    extensions: ['.js', '.ts', '.tsx', '.jsx', 'scss'],
  },
  module: ... // 省略
  optimization: ... // 省略
  plugins: [
    new MiniCssExtractPlugin({
      filename: `${outputFilename}.css`,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(process.cwd(), 'client/index.html'),
      filename: 'index.html',
      inject: 'body',
    }),
  ],
});
```
</div>
<div>

```.js:webpack/client/webpack.dev.js
const path = require('path');
const webpackMerge = require('webpack-merge');
const commonConf = require('./webpack.common');

const outputFilename = '[name]';

module.exports = () =>
  webpackMerge(commonConf({ outputFilename }), {
    mode: 'development',
    devtool: 'source-map',
  });
```
</div>

</details>



　ここでは、webpack が動くように、DIを意識せずに最低限で書いているが、実際には、[ServeStaticModule](https://github.com/nestjs/serve-static#api-spec)、及び`ServeStaticService（オリジナル）` を実装して、その`ServeStaticService`の中で、 `webpack-dev-middleware`を実装している。
#package.json への記述
　最後に、`package.json`へ、開発用サーバーを建てるとき・商用環境用にビルドするとき・商用環境を実際に始めるとき　用のコマンドを簡単に開始できるように記述しておく。

```.json:package.json
{
  ...
  "scripts": {
    "prebuild": "rimraf dist",
    "start:dev": "rimraf dist && webpack --config webpack/server/webpack.dev.js",
    "start:prod": "nohup node ./dist/server.js &",
    "build": "run-s build:*",
    "build:server": "webpack --config webpack/server/webpack.prod.js",
    "build:client": "webpack --config webpack/client/webpack.prod.js",
  }
  ...
}
```

　`run-p`は、[npm-run-all](https://www.npmjs.com/package/npm-run-all)　のパッケージを使用したら使えるコマンド。
　`package.json`の`scripts`に記述してあるコマンド群を指定して、逐次実行してくれる。

# 商用環境(production)での注意点
　本筋とは関係なく、あまり詳しく話すと長くなるので抽象的に記述するが、`webpack`を使用して`production`環境でビルドしたものへのサーバアクセス時に、[`@UseGuards`](https://docs.nestjs.com/guards) 周りの認証・認可で不具合が生じた。二つの __passport strategy__ 　([こちら](https://docs.nestjs.com/security/authentication) に書いてある二つ）を使用して認証・認可を実装してるのに、実際に起動してみると、片方しか起動していなかった。
　これは、`server/webpack.prod.js`ファイルに、

```.js:webpack/server/webpack.prod.js
// ...something
module.exports = webpackMerge(commonConf, {
  mode: 'production',
  optimization: {
    minimize: false, // 本来 mode: 'production'なら minimize: true に自動的になる
  },
})
```

　と記述することでうまくいった。商用なのに、コンパイルされたファイルは最小化されていないという改善点が残るが、背に腹は変えられないので、渋々これにしといた。
　これに限らず、`webpack` の `mode: production` で悩んでいる人（あんまり事例を聞いたことはないけど）は、`minimize: false` と記述すればうまくいくかもしれない？

# 感想
　今まで、`webpack` を使用するときは、`$create-react-app`や`$vue init` 等のフレームワークで用意されているものしか使用してこなかったから、自分で一から実装したのは初めて。
　結局、最後の不具合の、根本的原因はわからなかったので、`webpack`のコンパイル時にどういった挙動をしているのか追えたらいいのに・・・と思いました。