本教程使用基于开发教程的代码示例
- 模块热加载(HMR)是webpack提供的最有用的特性之一。它允许所有类型的模块能够实时更新而不用全部刷新。本页面只关注实现而 概念页面会提供更多关于它是如何工作以及为什么如此有用的细节。
HMR 并不打算用于生产环境,意味它只能在开发环境使用。查看生产环境构建指南了解更多。
启用HMR
- 该特性对于生产力来说是很好的。我们所需要做的就是更新我们的webpack-dev-server配置,并使用HMR插件内置的webpack。我们还将删除print.js的入口点,现在它将被index.js模块使用。
如果你选择了使用webpack-dev-middleware而不是webpack-dev-server的路由,请使用webpack-hot-middleware包在你的自定义服务器或应用程序上启用HMR。
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const webpack = require('webpack');
module.exports = {
entry: {
- app: './src/index.js',
- print: './src/print.js'
+ app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
+ hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
+ new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
你可以使用命令行工具执行以下命令:webpack-dev-server --hotOnly来修改webpack-dev-server的配置。
- 现在让我们更新index.js文件,这样当检测到print.js内部发生更改时,我们告诉webpack接受更新的模块。
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
var element = document.createElement('div');
var 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语句,你应该在浏览器会看到以下输出。
print.js
export default function printMe() {
- console.log('I get called from print.js!');
+ console.log('Updating print.js...')
}
console
[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
- 在使用Node.js API运行Webpack Dev服务器时,不要在webpack配置对象上设置dev server选项。相反,在创建时将它们作为第二个参数传递。例如:
new WebpackDevServer(compiler, options)
- 要启用HMR,你还需要修改webpack配置对象以包含HMR入口点。webpack-dev-server包包括一个名为addDevServerEntrypoints的方法,你可以使用它来完成这项工作。这里有一个小例子,说明了它是如何工作的:
const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const config = require('./webpack.config.js');
const options = {
contentBase: './dist',
hot: true,
host: 'localhost'
};
webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);
server.listen(5000, 'localhost', () => {
console.log('dev server listening on port 5000');
});
如果你使用webpack-dev-middleware,检查webpack-hot-middleware包以确保在你的自定义服务器启用HMR。
Gotchas
- 模块热加载可能会很棘手。为了说明这一点,让我们回到我们的工作示例。如果您继续点击示例页面上的按钮,你将会意识到控制台正在打印旧的printMe函数。
- 之所以会发生这种情况,是因为按钮的onclick事件处理程序仍然绑定到原来的printMe函数。
- 为了使它与HMR一起工作,我们需要使用module.hot.accept来更新这个绑定到新的printMe函数。
index.js
import _ from 'lodash';
import printMe from './print.js';
function component() {
var element = document.createElement('div');
var 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);
})
}
- 这只是一个例子,但还有很多其他的例子可以很容易地让人们掉坑里。幸运的是,有很多加载器(其中一些在下面提到)将使模块热加载变得更加容易。
HMR 和样式表
- 在style-loader的帮助下,使用CSS的模块热加载实际上是相当简单的。当CSS依赖项被更新时,这个加载器使用module.hot.accept在后台更新<style>标签。
- 首先,让我们用以下命令安装两个加载器
npm install --save-dev style-loader css-loader
- 为了使用加载器,修改配置文件
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
hot: true
},
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader']
+ }
+ ]
+ },
plugins: [
new CleanWebpackPlugin(['dist'])
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
- 样式表的热加载就像在模块中import一样简单。
project
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() {
var element = document.createElement('div');
var 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与各种框架和库之间的顺畅交互。
- React Hot Loader:实时调整react组件。
- Vue Loader:该加载器支持开箱即用的vue组件的HMR。
- Elm Hot Loader:支持Elm编程语言的HMR。
- Redux HMR:不需要任何加载器或插件!只需对主存储文件进行简单更改即可。
- Angular HMR:不需要任何加载器或插件!只需对主要的NgModule文件进行简单的更改即可完全控制HMR API。