HMRでファイルを変更してからブラウザで変更が反映されるまで
HMR(Hot Module Replacement)ってみなさん使ってますか?
HMRは、webpackの提供する仕組みで、ページの再読み込み無しで、モジュールの交換・追加・削除をアプリケーションの動作中にブラウザに適用してくれる開発ツールです。
ブラウザをリロードしなくても変更したモジュールだけが更新されるし、開発の時間が節約できて便利ですよね!
最近HMRってどうして変更したモジュールだけが更新されるのかなあって考えたことがあったのでまとめてみました。
HMRの仕組み
HMRでファイルを変更してからブラウザで変更が反映されるまでの流れは以下のような仕組みになっています。
Webpackのコア、プラグインライブラリ Webpack-dev-server関連のライブラリ HMRライブラリ(react-hot-loader、style-loader等) 変更したファイル(js, css等) アプリ(ブラウザ上で確認できるもの)
1~2. ファイルの変更
webpackはファイルの変更を監視しています。 ファイルを変更すると、Webpackは変更を取得し、HotModuleReplacementPluginに通知します。
3. manifestとchunkの作成
WebpackはHotModuleReplacementPluginを使用して manifestファイル(変更されたモジュールのリストを含むJSON)と updated chunkファイル(実際の変更情報を含むjs)を生成します。
4. Webpack-dev-serverに変更を通知
WebpackはWebpack-dev-serverに変更を通知します。
5. Webpack-dev-server/clientに変更を通知
Webpack-dev-serverは、WebSocketを介して「無効な」通知を送信することで、Webpack-dev-server/clientに変更を通知します。
Webpack-dev-server/clientは、アプリが最初にhot/dev-serverライブラリに読み込まれたときに取得した初期ハッシュ(例:c6202ec89ecd4e669b46)をWebpack-dev-serverに送信します。
Webpack-dev-server/clientとは?
WebSocketを介してブラウザーで実行されているJSファイル。
Webpack-dev-server関連のライブラリ。
6~8. manifestファイルのダウンロード
Webpack-dev-server/clientはhot/dev-serverに変更を通知し、hot/dev-serverはWebpackによって挿入されたJSONP runtimeを呼び出して、3で生成したmanifestファイルをダウンロードします。
manifestファイルには、chunkに関する詳細が含まれています。例えば、ファイル名はc6202ec89ecd4e669b46.hot-update.json
で、中身は以下のようになります。
{h: "0355e4eec902c32b2d0b", c: {0: true}}
9. updateファイルのダウンロード
JSONP runtimeはmanifestファイル内に含まれる情報を使用して、すべてのupdateファイル(js)をダウンロードします。
変更に関する情報が含まれていて、DOMに追加されて実行されます。
updateファイル(js)の中身は以下のような感じ。
webpackHotUpdate(0,{ /***/ 116: /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react__ = __webpack_require__(85); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_react__); //# sourceMappingURL=0.c6202ec89ecd4e669b46.hot-update.js.map
10. HMR runtimeの呼び出し
updateファイルは、モジュールID(上記の例ではmodule id 116)と変更内容を使用してHMR runtimeを呼び出します。
11. loaderにを変更を委任
HMR runtime自体は、変更を適用する方法を知らないので、Reactであればreact-hot-loader、cssであればstyle-loaderなどの対応するloaderにを変更を適用してもらうようにお願いします。
12. モジュールの更新
変更の適用に問題(構文エラー等)がない場合は、モジュールが更新されます。 ここでやっとモジュールファイルを変更してブラウザ上で変更の確認することができるのです。
もし問題があれば、hot/dev-serverにエラーが通知されます。
HMRの変更を実際に見てみる
次は、実際にHMRを使うとブラウザではどのような動きになっているのか見ていきます。
さくっと動きを確認したいのでReact Hot Loaderのboilerplateを使って実際にHMRで変更したときの流れを見てみることにします。
1. Webpack-dev-serverを起動
まずは、以下からReact Hot Loaderのboilerplateを落としてきてWebpack-dev-serverを立ち上げます。
Webpack-dev-serverを起動するとコンソール上に以下のようなログが表示されます。
[HMR]
はWebpack/hot/dev-serverのログ
[WDS]
はWebpack-dev-serverのログ
になります。
2. ファイルを変更
src/containers/Root.js
を変更してみます。
とりあえずHello React Hot Loader!
→Hello World!
に変更してみました。
import React from "react"; const Root = () => <div>Hello World!</div>; export default Root;
3. サーバーとクライアントでのやり取り
ファイルの変更後、dev toolのNetworkタブのWebSocket欄を確認するとハッシュを送信したりしてることが分かります。 webpack-dev-serverは、WebSocketを介してファイルの変更についてクライアントと常に通信しているみたいです。
4. 変更をアップロード
次にdev toolのNetworkタブ欄を確認すると、webSocketを介して得た現在のハッシュ(001efdfaf0c0e6150d7f)を使用して、manifestファイル(001efdfaf0c0e6150d7f.hot-update.json
)をダウンロードしていることが分かります。
また、manifestファイル内に含まれる情報を使用して、updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js
)をダウンロードしていることも分かります。
manifestファイル(001efdfaf0c0e6150d7f.hot-update.json
)の内容は以下。
5. 変更内容を更新
updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js
)を確認するとファイル内にwebpackHotUpdate
関数があり、
このwebpackHotUpdate
関数を呼び出すことでHMR runtimeに変更内容が渡されます。
HMR runtimeは、この変更内容をReact Hot Loaderに渡し、変更内容が更新され変更内容がブラウザで確認することができます。
updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js
)
まとめ
普段何気なく使ってるHMRですが、改めてHMRって何だろうって考えたときにリロードしなくても変更したモジュールだけ変更できるのってすごくない…!! って思ったのがきっかけで仕組みを調べてみました。
仕組みを少し理解できたのでWebpackの設定周りが理解しやすくなった気がしています。
参考