ホットモジュール置換(HMR)は、webpackが提供する最も便利な機能の1つです。これにより、完全なリフレッシュを行う必要なく、実行時にあらゆる種類のモジュールを更新できます。このページでは**実装**に焦点を当てており、概念ページでは、その仕組みと有用性について詳しく説明しています。
この機能は生産性に最適です。必要なのは、webpack-dev-serverの設定を更新し、webpackの組み込みHMRプラグインを使用することだけです。また、print.js
のエントリポイントを削除します。これは、index.js
モジュールによって消費されるようになるためです。
webpack-dev-server
v4.0.0以降、ホットモジュール置換はデフォルトで有効になっています。
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
- print: './src/print.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
+ hot: true,
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
HMRのエントリポイントを手動で指定することもできます
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const webpack = require("webpack");
module.exports = {
entry: {
app: './src/index.js',
- print: './src/print.js',
+ // Runtime code for hot module replacement
+ hot: 'webpack/hot/dev-server.js',
+ // Dev server client for web socket transport, hot and live reload logic
+ client: 'webpack-dev-server/client/index.js?hot=true&live-reload=true',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
+ // Dev server client for web socket transport, hot and live reload logic
+ hot: false,
+ client: false,
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
+ // Plugin for hot module replacement
+ new webpack.HotModuleReplacementPlugin(),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
次に、index.js
ファイルを更新して、print.js
内の変更が検出されたときに、webpackに更新されたモジュールの受け入れを指示します。
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
document.body.appendChild(component());
+
+ if (module.hot) {
+ module.hot.accept('./print.js', function() {
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ }
print.js
のconsole.log
文を変更し始めると、ブラウザコンソールに次の出力が表示されます(今のところbutton.onclick = printMe
出力は気にしないでください。後でその部分も更新します)。
print.js
export default function printMe() {
- console.log('I get called from print.js!');
+ console.log('Updating print.js...');
}
コンソール
[HMR] Waiting for update signal from WDS...
main.js:4395 [WDS] Hot Module Replacement enabled.
+ 2main.js:4395 [WDS] App updated. Recompiling...
+ main.js:4395 [WDS] App hot update...
+ main.js:4330 [HMR] Checking for updates on the server...
+ main.js:10024 Accepting the updated printMe module!
+ 0.4b8ee77….hot-update.js:10 Updating print.js...
+ main.js:4330 [HMR] Updated modules:
+ main.js:4330 [HMR] - 20
Node.js APIを使用してWebpack Dev Serverを使用する場合は、devサーバーのオプションをwebpack設定オブジェクトに配置しないでください。代わりに、作成時に2番目のパラメータとして渡します。例:
new WebpackDevServer(options, compiler)
HMRを有効にするには、webpack設定オブジェクトを修正してHMRエントリポイントを含める必要があります。その方法の簡単な例を以下に示します。
dev-server.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const webpackDevServer = require('webpack-dev-server');
const config = {
mode: 'development',
entry: [
// Runtime code for hot module replacement
'webpack/hot/dev-server.js',
// Dev server client for web socket transport, hot and live reload logic
'webpack-dev-server/client/index.js?hot=true&live-reload=true',
// Your entry
'./src/index.js',
],
devtool: 'inline-source-map',
plugins: [
// Plugin for hot module replacement
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
const compiler = webpack(config);
// `hot` and `client` options are disabled because we added them manually
const server = new webpackDevServer({ hot: false, client: false }, compiler);
(async () => {
await server.start();
console.log('dev server is running');
})();
webpack-dev-server
Node.js APIの完全なドキュメントを参照してください。
ホットモジュール置換は難しい場合があります。これを示すために、動作している例に戻りましょう。例ページのボタンをクリックすると、古いprintMe
関数がコンソールに出力されていることに気付くでしょう。
これは、ボタンのonclick
イベントハンドラが元のprintMe
関数にまだバインドされているためです。
HMRでこれを機能させるには、module.hot.accept
を使用して新しいprintMe
関数にそのバインディングを更新する必要があります。
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe; // onclick event is bind to the original printMe function
element.appendChild(btn);
return element;
}
- document.body.appendChild(component());
+ let element = component(); // Store the element to re-render on print.js changes
+ document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
- printMe();
+ document.body.removeChild(element);
+ element = component(); // Re-render the "component" to update the click handler
+ document.body.appendChild(element);
})
}
これは1つの例に過ぎませんが、他にも多くの人が簡単につまずく可能性のある例があります。幸い、多くのローダー(そのいくつかは以下に記載されています)があり、ホットモジュール置換をはるかに簡単にします。
style-loader
を使用すると、CSSでのホットモジュール置換(HMR)は非常に簡単です。このローダーは、CSSの依存関係が更新された際に<style>
タグを修正するために、内部的にmodule.hot.accept
を使用します。
最初に、以下のコマンドで両方のローダーをインストールしましょう。
npm install --save-dev style-loader css-loader
次に、ローダーを使用するように設定ファイルを更新しましょう。
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
},
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
スタイルシートのホットローディングは、モジュールにインポートすることで実行できます。
プロジェクト
webpack-demo
| - package.json
| - webpack.config.js
| - /dist
| - bundle.js
| - /src
| - index.js
| - print.js
+ | - styles.css
styles.css
body {
background: blue;
}
index.js
import _ from 'lodash';
import printMe from './print.js';
+ import './styles.css';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe; // onclick event is bind to the original printMe function
element.appendChild(btn);
return element;
}
let element = component();
document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
document.body.removeChild(element);
element = component(); // Re-render the "component" to update the click handler
document.body.appendChild(element);
})
}
body
のスタイルをbackground: red;
に変更すると、ページの背景色が完全に更新されることなくすぐに変化するはずです。
styles.css
body {
- background: blue;
+ background: red;
}
コミュニティには、HMRを様々なフレームワークやライブラリとスムーズに連携させるための、多くのローダーと例があります…
ng serve
コマンドに--hmr
フラグを追加します。