dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

webpack-dev-serverをExpressに組み込んで使う(webpack-dev-middleware, webpack-hot-middleware)

はじめに

1年以上前ですがこんな記事を書きました。

このときは webpack-dev-serverスタンドアロンなサーバーとして使う方法しか知らなかったのですが
既存の Express アプリに組み込んで使うこともできます。
そのためには webpack-dev-middleware および HMR のために webpack-hot-middleware を使います。

今回はその設定手順をメモ。


公式ドキュメント

Webpack v1: https://webpack.github.io/docs/webpack-dev-middleware.html
Webpack v2: https://webpack.js.org/guides/development/#webpack-dev-middleware


設定手順

パッケージをインストールする
$ npm install -D webpack-dev-middleware webpack-hot-middleware


webpack.config.js を修正する

以下のように2箇所修正します。(ES2015 で書いてます)

 // webpack.config.dev.babel.js
 import webpack from 'webpack';

 export default {
   entry: [
+    'webpack-hot-middleware/client',
     './index',
   ],
 
   // Configuration for dev server
   devServer: {
     contentBase: path.resolve(path.join(__dirname, 'public')),
     port: 3000,
   },
 
   devtool: 'cheap-module-eval-source-map',
   plugins: [
       ...
+     new webpack.HotModuleReplacementPlugin(),
   ],
 });


Express アプリ内で middleware を読み込む

続いて、Express アプリ(ここでは server.js)内で上述した2つの middleware を使用します。
(サーバーサイドも ES2015 で書いてる想定です)

 // server.js
 import express from 'express';
 import path from 'path';
 
 const app = express();
 const port = process.env.PORT || 8080;
 
+if (process.env.NODE_ENV !== 'production') {
+  const webpack = require('webpack');
+  const webpackDevMiddleware = require('webpack-dev-middleware');
+  const webpackHotMiddleware = require('webpack-hot-middleware');
+  const config = require('./webpack.config.dev.babel').default;
+  const compiler = webpack(config);
+
+  app.use(webpackHotMiddleware(compiler));
+  app.use(webpackDevMiddleware(compiler, {
+    noInfo: true,
+    publicPath: config.output.publicPath,
+  }));
+}
 
 // Serve static files
 app.use(express.static(path.join(__dirname, 'public')));
 
 console.log(`Served: http://localhost:${port}`);
 app.listen(port, (err) => {
   if (err) {
     console.error(err);
   } else {
     console.info(`==> 🌎  Listening on port ${port}. Open up http://localhost:${port}/ in your browser.`);
   }
 });

基本的には webpack の config ファイルを読み込んで一度 webpack() に渡した後、それ(compiler)をそれぞれの middleware に引数として渡してあげればよいです。

また、webpackDevMiddleware の第二引数に渡しているオプションは

  • noInfo:info レベルのログを console に出力するかどうか。true(しない)にしてる人が多い印象
  • publicPath(必須): webpack.config.js の output.publicPath を指定しておけば OK

それ以外のオプションについては README を参照してください。

NOTE:
webpack.config を ES5 で書いてる場合

const config = require('./webpack.config.dev.babel').default;

.default は不要です。

また、これらの middleware は本番環境では無効にしておく必要があるため
process.env.NODE_ENV で切り替えができるようにしておきます。


サーバーを起動する

設定は以上です。後は package.json

"scripts": {
  "start": "babel-node server.js"
}

などと書き、

$ npm start

でサーバーを起動すると webpack-dev-server および HMR が有効になります。


注意事項

ここまでで開発時の設定としては十分なのですが、この webpack.config.js で本番環境用にビルドをすると
entryplugins に HMR が含まれているため実行時にエラーになります。

GET http://localhost:8080/__webpack_hmr 404 (Not Found)

そのため、実際には開発用(dev server 用)と本番環境向けビルド用に config ファイルを分けるか、または条件分岐を行う必要があります。

このあたりはサンプルコードを作ったので参考にしてください。

config ファイルを

  • webpack.config.base.babel.js:開発用でも本番環境用でも共通の設定
  • webpack.config.dev.babel.js:開発用の設定(↑で挙げたような)を記載
  • webpack.config.prod.babel.js:本番環境用の設定(圧縮など)を記載

というように3つに分割し、webpack-merge を使って base を残り2つにマージする形でそれぞれの環境用の config を生成しています。