フロントエンドエンジニアをやっています。
頑張るから読んでほしい。

HMRでファイルを変更してからブラウザで変更が反映されるまで

HMR(Hot Module Replacement)ってみなさん使ってますか?

HMRは、webpackの提供する仕組みで、ページの再読み込み無しで、モジュールの交換・追加・削除をアプリケーションの動作中にブラウザに適用してくれる開発ツールです。

ブラウザをリロードしなくても変更したモジュールだけが更新されるし、開発の時間が節約できて便利ですよね!

最近HMRってどうして変更したモジュールだけが更新されるのかなあって考えたことがあったのでまとめてみました。

HMRの仕組み

HMRでファイルを変更してからブラウザで変更が反映されるまでの流れは以下のような仕組みになっています。

f:id:cidermitaina:20200406002555p:plain

f:id:cidermitaina:20200406103524p:plain Webpackのコア、プラグインライブラリ f:id:cidermitaina:20200406103749p:plain Webpack-dev-server関連のライブラリ f:id:cidermitaina:20200406103927p:plain HMRライブラリ(react-hot-loader、style-loader等) f:id:cidermitaina:20200406104139p:plain 変更したファイル(js, css等) f:id:cidermitaina:20200406104022p:plain アプリ(ブラウザ上で確認できるもの)

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を立ち上げます。

github.com

Webpack-dev-serverを起動するとコンソール上に以下のようなログが表示されます。

f:id:cidermitaina:20200406005855p:plain

[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を介してファイルの変更についてクライアントと常に通信しているみたいです。

f:id:cidermitaina:20200406010904p:plain

4. 変更をアップロード

次にdev toolのNetworkタブ欄を確認すると、webSocketを介して得た現在のハッシュ(001efdfaf0c0e6150d7f)を使用して、manifestファイル(001efdfaf0c0e6150d7f.hot-update.json)をダウンロードしていることが分かります。 また、manifestファイル内に含まれる情報を使用して、updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js)をダウンロードしていることも分かります。

manifestファイル(001efdfaf0c0e6150d7f.hot-update.json)の内容は以下。

f:id:cidermitaina:20200406011556p:plain

5. 変更内容を更新

updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js)を確認するとファイル内にwebpackHotUpdate関数があり、 このwebpackHotUpdate関数を呼び出すことでHMR runtimeに変更内容が渡されます。

HMR runtimeは、この変更内容をReact Hot Loaderに渡し、変更内容が更新され変更内容がブラウザで確認することができます。

updadeファイル(0.001efdfaf0c0e6150d7f.hot-update.js)f:id:cidermitaina:20200406012135p:plain

まとめ

普段何気なく使ってるHMRですが、改めてHMRって何だろうって考えたときにリロードしなくても変更したモジュールだけ変更できるのってすごくない…!! って思ったのがきっかけで仕組みを調べてみました。

仕組みを少し理解できたのでWebpackの設定周りが理解しやすくなった気がしています。



参考

Hot Module Replacement | webpack

Webpack & The Hot Module Replacement - rajaraodv - Medium