webpackによるHot Module Replacement (HMR)
このサイトは作成途中のものを公開しています。 また、Webpackのバージョンが1.13.0の頃に作成されたものですが、2017年2月現在Webpackは1系を非推奨として2の使用を勧めています。
Hot Module Replacement (HMR)は、まだ実験的な機能であることに注意して下さい。
導入・入門
Hot Module Replacement (HMR)は、ページの再読み込み無しで、モジュールの交換・追加・削除をアプリケーションの動作中に行います。
必須項目
- プラグインの使用方法
- コード分割
- webpack-dev-server
どのように動作するのか?
Webpackはビルド処理中にアプリケーション内部で動作する小さなHMRランタイムをバンドルに追加します。 ビルドが完了すると、webpackは終了せずにアクティブの状態を継続し、ソースファイルの変更を監視します。 webpackがソースファイルの変更を検知すると、変更があったモジュールだけを再ビルドします。
webpackがHMRランタイムへ信号を送るか、HMRランタイムが変更をポーリングするかを設定によって選ぶことが出来ます。 いずれにせよ、変更されたモジュールはHMRランタイムに送られ、ホット・アップデートの適用が試みられます。
まず、更新されるモジュールが依存されているかどうかが調べられます。 もしそうでなければ、更新されたモジュールをrequireするモジュールがチェックされます。 これらのモジュールも同様に影響を受ける場合は、変更されたモジュールをrequireするモジュールをrequireする…といったように、 別の層へそれが伝搬していきます。
この伝播は更新が影響を及ぼさないところまで到達するか、 またはアプリケーションのエントリーポイントに到達する(ホット・アップデート失敗)まで続けられます。 (訳注: ホット・アップデートはモジュールだけを更新する目的があるため、エントリーポイントを更新することは、 すなわちホット・アップデートの失敗ということだと考えられます。)
アプリケーションの視点から
- アプリケーションのコードは、更新のチェックをするようにHMRランタイムに求めます。
- HMRランタイムは更新(非同期処理で)をダウンロードし、アプリケーション・コードに更新が利用可能なことを伝えます。
- アプリケーションのコードは、更新を適用するようにHMRランタイムに求めます。
- HMRラインタイムは更新(同期処理で)を適用します。
- アプリケーションのコードは、この処理でユーザーの操作を必要とするかもしれないし、必要としないかもしれません。(開発者が決めます)
コンパイラ(webpack)の視点から
標準のアセットに加えて、コンパイラは前のバージョンから現在のバージョンへの更新を可能にする、 "更新"(Update)の発行を必要とします。 "更新"(Update)には2つのパートが含まれます。
- 更新のマニフェスト(json)
- 1つまたは複数の更新のチャンク(js)
マニフェストには、新しくコンパイルされたもののハッシュと、更新された全てのチャンクのリストが含まれます(2.)。
この更新されたチャンクには、このチャンクで更新された全てのモジュールのためのコードが含まれます。 (または、モジュールが削除された場合はフラグ)
また、コンパイラはモジュールとチャンクのidがこれらのビルド間で一貫していることを保証します。 これは、ビルド間で"records"のjsonファイルを使用して、それらを格納します。(またはメモリに格納)
モジュールからの視点
HMRは選択可能な機能であるため、HMRコードが含まれたモジュールにのみ作用します。 ドキュメントには、モジュール内で利用可能なAPIについての説明が書かれています。 一般的にモジュールの開発者は、モジュールの更新に依存があった場合に呼びだされるハンドラーの処理を書きます。 またモジュールの開発者は、モジュールが更新された際に呼び出されるハンドラーをの処理を書くことも可能です。
ほとんどのケースで、各モジュールでHMRコードを書くことを強制されることはありません。 もし、モジュールがHMRハンドラを持たない場合、更新は伝搬されます。 これは単一のハンドラが全体のモジュール・ツリーの更新を扱うことが出来る、ということを意味します。 もし、このツリーの単一のモジュールが更新されると、全体のモジュール・ツリーは再読み込みされます。 (再読み込みのみで、置き換え(transferred)ではありません)
HMRランタイムからの(技術的な)視点
モジュール・システムのランタイム用のものは、parents
とchildren
のモジュールを追跡をする追加コードになります。
管理側から見ると、このランタイムは2つのメソッドcheck
とapply
をサポートします。
check
はマニフェストの更新のためにHTTPリクエストを送ります。
このリクエストが失敗した場合は、更新するものが無いということになります。
そうでなければ更新されたチャンクのリストが、現在読み込まれているチャンクのリストと比較されます。
読み込まれた各チャンクに対応する更新チャンクがダウンロードされます。
全てのモジュール更新は、更新時にランタイム内で保存されます。
ランタイムは、更新がダウンロードされたことを意味するready
状態に切り替わり、
適用する準備が整った状態になります。
ready
状態での、それぞれの新しいチャンクのリクエストに対しても、
更新チャンクがダウンロードされます。
apply
メソッドは、全ての更新されたモジュールに対して、無効(invalid)とするフラグを付けます。
無効とされたモジュールは、モジュール内でのハンドラの更新、または各親でのハンドラの更新が必要となります。
もしくは無効とされたモジュールを一まとめ(bundle up)にし、全ての親も無効として印付けを行います。
この処理は、"伝搬(bubbling up)"の発生が無くなるまで続きます。
もし、この伝播がエントリーポイントから起こった場合、この処理は失敗となります。
これで、全ての無効モジュールが判明し、取り除かれます。(翻訳に自信なし)
次に、現在のハッシュが更新され、全ての"accept"ハンドラが呼び出されます。
ランタイムはidle
状態に戻り、通常の処理が継続されます。
生成されたファイル(技術的な説明)
左の図は、初回時のコンパイラの経過を表しています。
右の図は、更新されるモジュール4と9が追加された際の経過を表しています。
これを使って何が出来ますか?
開発時に、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
を追加するだけです。
あるいは、check
とapply
を呼び出す幾つかの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.check
、module.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が少し変更されています。)
© 2010 - 2017 STUDIO KINGDOM