webpackによるHot Module Replacement (HMR)

このサイトは作成途中のものを公開しています。 また、Webpackのバージョンが1.13.0の頃に作成されたものですが、2017年2月現在Webpackは1系を非推奨として2の使用を勧めています。

Hot Module Replacement (HMR)は、まだ実験的な機能であることに注意して下さい。

導入・入門

Hot Module Replacement (HMR)は、ページの再読み込み無しで、モジュールの交換・追加・削除をアプリケーションの動作中に行います。

必須項目

どのように動作するのか?

Webpackはビルド処理中にアプリケーション内部で動作する小さなHMRランタイムをバンドルに追加します。 ビルドが完了すると、webpackは終了せずにアクティブの状態を継続し、ソースファイルの変更を監視します。 webpackがソースファイルの変更を検知すると、変更があったモジュールだけを再ビルドします。

webpackがHMRランタイムへ信号を送るか、HMRランタイムが変更をポーリングするかを設定によって選ぶことが出来ます。 いずれにせよ、変更されたモジュールはHMRランタイムに送られ、ホット・アップデートの適用が試みられます。

まず、更新されるモジュールが依存されているかどうかが調べられます。 もしそうでなければ、更新されたモジュールをrequireするモジュールがチェックされます。 これらのモジュールも同様に影響を受ける場合は、変更されたモジュールをrequireするモジュールをrequireする…といったように、 別の層へそれが伝搬していきます。

この伝播は更新が影響を及ぼさないところまで到達するか、 またはアプリケーションのエントリーポイントに到達する(ホット・アップデート失敗)まで続けられます。 (訳注: ホット・アップデートはモジュールだけを更新する目的があるため、エントリーポイントを更新することは、 すなわちホット・アップデートの失敗ということだと考えられます。)

アプリケーションの視点から
  1. アプリケーションのコードは、更新のチェックをするようにHMRランタイムに求めます。
  2. HMRランタイムは更新(非同期処理で)をダウンロードし、アプリケーション・コードに更新が利用可能なことを伝えます。
  3. アプリケーションのコードは、更新を適用するようにHMRランタイムに求めます。
  4. HMRラインタイムは更新(同期処理で)を適用します。
  5. アプリケーションのコードは、この処理でユーザーの操作を必要とするかもしれないし、必要としないかもしれません。(開発者が決めます)
コンパイラ(webpack)の視点から

標準のアセットに加えて、コンパイラは前のバージョンから現在のバージョンへの更新を可能にする、 "更新"(Update)の発行を必要とします。 "更新"(Update)には2つのパートが含まれます。

  1. 更新のマニフェスト(json)
  2. 1つまたは複数の更新のチャンク(js)

マニフェストには、新しくコンパイルされたもののハッシュと、更新された全てのチャンクのリストが含まれます(2.)。

この更新されたチャンクには、このチャンクで更新された全てのモジュールのためのコードが含まれます。 (または、モジュールが削除された場合はフラグ)

また、コンパイラはモジュールとチャンクのidがこれらのビルド間で一貫していることを保証します。 これは、ビルド間で"records"のjsonファイルを使用して、それらを格納します。(またはメモリに格納)

モジュールからの視点

HMRは選択可能な機能であるため、HMRコードが含まれたモジュールにのみ作用します。 ドキュメントには、モジュール内で利用可能なAPIについての説明が書かれています。 一般的にモジュールの開発者は、モジュールの更新に依存があった場合に呼びだされるハンドラーの処理を書きます。 またモジュールの開発者は、モジュールが更新された際に呼び出されるハンドラーをの処理を書くことも可能です。

ほとんどのケースで、各モジュールでHMRコードを書くことを強制されることはありません。 もし、モジュールがHMRハンドラを持たない場合、更新は伝搬されます。 これは単一のハンドラが全体のモジュール・ツリーの更新を扱うことが出来る、ということを意味します。 もし、このツリーの単一のモジュールが更新されると、全体のモジュール・ツリーは再読み込みされます。 (再読み込みのみで、置き換え(transferred)ではありません)

HMRランタイムからの(技術的な)視点

モジュール・システムのランタイム用のものは、parentschildrenのモジュールを追跡をする追加コードになります。

管理側から見ると、このランタイムは2つのメソッドcheckapplyをサポートします。

checkはマニフェストの更新のためにHTTPリクエストを送ります。 このリクエストが失敗した場合は、更新するものが無いということになります。 そうでなければ更新されたチャンクのリストが、現在読み込まれているチャンクのリストと比較されます。

読み込まれた各チャンクに対応する更新チャンクがダウンロードされます。 全てのモジュール更新は、更新時にランタイム内で保存されます。 ランタイムは、更新がダウンロードされたことを意味するready状態に切り替わり、 適用する準備が整った状態になります。

ready状態での、それぞれの新しいチャンクのリクエストに対しても、 更新チャンクがダウンロードされます。

applyメソッドは、全ての更新されたモジュールに対して、無効(invalid)とするフラグを付けます。 無効とされたモジュールは、モジュール内でのハンドラの更新、または各親でのハンドラの更新が必要となります。 もしくは無効とされたモジュールを一まとめ(bundle up)にし、全ての親も無効として印付けを行います。 この処理は、"伝搬(bubbling up)"の発生が無くなるまで続きます。 もし、この伝播がエントリーポイントから起こった場合、この処理は失敗となります。

これで、全ての無効モジュールが判明し、取り除かれます。(翻訳に自信なし) 次に、現在のハッシュが更新され、全ての"accept"ハンドラが呼び出されます。 ランタイムはidle状態に戻り、通常の処理が継続されます。

生成されたファイル(技術的な説明)

左の図は、初回時のコンパイラの経過を表しています。
右の図は、更新されるモジュール4と9が追加された際の経過を表しています。

Hmr

これを使って何が出来ますか?

開発時に、LiveReloadの代わりにこの仕組みを使用します。 実をいうとwebpack-dev-serverは、ページ全体の再読み込みを試みる前に、 HMRを使用した更新を更新を試みるhotモードをサポートします。 必要なことはwebpack/hot/dev-serverのエントリー・ポイントを追加し、--hot付きでdev-serverを呼び出すだけです。

webpack/hot/dev-serverはHMRの更新が失敗すると、ページ全体を再読み込みします。 ページの再読み込みを独自に制御したい場合は、代わりにwebpack/hot/only-dev-serverのエントリー・ポイントを追加することが可能です。

また、更新の仕組みとして製品版でも使用することが可能です。 その場合は、HMRとアプリケーションを統合するための管理コードを自身で書く必要があるでしょう。

幾つかのローダーは既にHMR可能なモジュールを生成するようになっています。(例: style-loader) このようなケースでは、あなたは特別何かをする必要はありません。

使用するためには何が必要ですか?

モジュールは、あなたがそれを"accept"した場合にのみ、更新されることが出来ます。 そのためにはmodule.hot.acceptに、親のまたは親の親のモジュールが必要になります。 例えば、ルーター(router)やサブビュー(subview)が適しているでしょう。

もし、webpack-dev-serverでのみ使用したい場合は、エントリー・ポイントとしてwebpack/hot/dev-serverを追加するだけです。 あるいは、checkapplyを呼び出す幾つかのHMR管理コードが必要になります。

処理の最中にモジュールidを追跡するために、コンパイラの記録(records)を有効にする必要があります。 (watchモードとwebpack-dev-serverはメモリ内で記録を保持するため、開発時にこれは必要とされません。)

HMRランタイムを追加してもらうために、コンパイラのHMRを有効にする必要があります。

何が優れているのですか?

  • LiveReloadのようですが、これが各モジュールに備わっていて、個別にやり取りが出来ます。
  • 製品版に使用することが可能です。
  • 更新はコード分割に考慮して、アプリケーションの変更部分だけのダウンロードと更新を行います。
  • アプリケーションのある部分だけに使用することが可能で、その場合は他のモジュールに影響しません。
  • HMRが無効の場合は、全てのHMRコードがコンパイラによって取り除かれます。(if(module.hot)でそれを囲んでいます。)

警告

  • これは試験的な機能で、完全なテストがされているわけではありません。
  • 幾つかのバグが存在する可能性があります。
  • 理論的には製品版で使用することが出来ますが、厳格に運用するケースには時期尚早かもしれません。
  • モジュールidはコンパイルの最中に追跡される必要があるため、これを保持(records)しておく必要があります。
  • 最初のコンパイルの後、Optimizerはモジュールidをそれ以上最適化することが出来ません。 そのため、バンドルのサイズに少し影響を与えます。
  • HMRランタイムのコードにより、バンドルサイズが増加します。
  • 製品版で使用する場合には、HMRハンドラをテストするための追加のテストが必要になります。 これはかなり困難であると考えられます。

チュートリアル

webpackでhotコード置換を使用するためには、以下の4つのことが必要になります。

  • records (--records-path, recordsPath: ...)
  • グローバルでのhotコード置換(HotModuleReplacementPlugin)の有効化
  • hotコード置換するコードにmodule.hot.accept
  • hotコード置換を管理するコードにmodule.hot.checkmodule.hot.apply

下記は小さなテストケースになります。

/* style.css */
body {
    background: red;
}
/* entry.js */
require("./style.css");
document.write("<input type='text' />");

dev-serverをしようして、hotコード置換を使用するには十分でしょう。

npm install webpack webpack-dev-server -g
npm install webpack css-loader style-loader
webpack-dev-server ./entry --hot --inline --module-bind "css=style\!css"

dev-serverはinメモリーレコードで提供され、これは開発に向いています。

--hotはhotコード置換の有効/無効を切り替えます。

これは、HotModuleReplacementPluginを追加します。 --hotフラグ、またはwebpack.config.js内でHotModuleReplacementPluginを使用していることを確認して下さい。 ただし、同時に両方を使用することは出来ません。 HMRプラグインが重複して追加されると、セットアップは壊れてしまうでしょう。

webpack/hot/dev-serverに、dev-serverのための特別な管理コードが存在し、 --inlineによって自動的に追加されます。 (webpack.config.jsにこれを追加する必要はありません。)

style-loaderは既にhot置換コードを含んでいます。

http://localhost:8080/bundleにアクセスすれば、赤い背景色と入力欄が置かれたページを確認することが出来るはずです。 テキストを入力欄に打ち込んだ後、style.cssを別の色になるように編集して下さい。

ページ全体を再読み込みすること無く、背景色が更新されました...! 入力欄のテキストと選択状態は、そのままになっているはずです。

hot置換(管理)コードを自分で書く方法を知りたければ、Hot Module Replacementを参照して下さい。

コーディング無しでデモを見たければ、example-appをチェックしてください。 (注意: これは少し古いので、ソースコードは見ないで下さい。HMRのAPIが少し変更されています。)

 Back to top

© 2010 - 2017 STUDIO KINGDOM