一、概念介绍
模块热替换(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新所有类型的模块,而无需完全刷新。
主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器devtools直接更改样式。
注意:HMR只能被应用到开发环境中。
本文示例代码运行环境:
node:12.20.1、webpack:5.37.1、webpack-cli:4.7.0、webpack-dev-server:3.11.2。
二、使用方式
- 更新webpack.config.js配置。
+ devServer: {
+ port: 9000,
+ host: '127.0.0.1',
+ hot: true,
+ },
plugins: [
// 定义了devServer.hot:true后,可以省略不写,详细原因可以查看本文四、源码分析 1.1 -> webpack-dev-server/lib/utils/addEntries.js 的代码
// new webpack.HotModuleReplacementPlugin()
]
- package.json添加scripts。
webpack-cli提供了三个启动webpack-dev-server命令:webpack serve、webpack s、webpack server,它们的作用是相同的。详细原因可以查看本文四、源码分析 -> webpack-cli/lib/webpack-cli.js的代码。
"scripts": {
+ "dev": "webpack serve",
}
- 在index.js中新增module.hot?.accept方法。
function render() {
root.innerHTML = require('./print.js')
}
render()
+ module.hot?.accept(['./print.js'], render)
- 修改print.js。
function printMe() {
- console.log('Updating print.js');
+ console.log('Updating print.js 1');
}
module.exports = printMe
三、源码调试
- package.json中增加scripts。
"scripts": {
+ "debug": "node ./node_modules/.bin/webpack serve"
}
- vscode中打开debug面板,新建一个launch.json配置,启动debug。
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "debug",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script",
"debug"
],
"skipFiles": [
"<node_internals>/**"
],
// cwd设置为需要调试项目的根目录
"cwd": "${workspaceFolder}/webpack/hmr/test"
}
]
}
四、源码分析
以下内容主要是以执行webpack serve命令后,各个模块(文件)主要做了什么事情,来展开阐述。
webpack serve
-> ./node_modules/./bin/webpack中runCli (line 48)
const runCli = cli => {
const pkg = require('webpack-cli/package.json');
require('webpack-cli/bin/cli.js');
};
-> webpack-cli/bin/cli.js (line 25)
runCLI(process.argv);
-> webpack-cli/lib/bootstrap.js (line 4)
const cli = new WebpackCLI();
await cli.run(args);
-> webpack-cli/lib/webpack-cli.js (line 783)
const externalBuiltInCommandsInfo = [
{
name: 'serve [entries...]',
alias: ['server', 's'],
pkg: '@webpack-cli/serve',
}]
const loadCommandByName = async (commandName) => {
require(pkg);
}
this.webpack = require('webpack');
// line 1834
createCompiler(options, callback) {
let compiler = this.webpack(options}
return compiler;
}
-> @webpack-cli/serve/index.js (line 6)
const startDevServer_1 = __importDefault(require("./startDevServer"));
// line 81
const compiler = await cli.createCompiler();
await startDevServer_1.default(compiler);
-> @webpack-cli/serve/startDevServer.js (line 92)
Server = require('webpack-dev-server/lib/Server');
const server = new Server(compiler, options);
server.listen(options.port, options.host, (error) => {
if (error) {
throw error;
}
});
-> webpack-dev-server/lib/Server.js (line 92)
// 修改entry和plugins line 72
updateCompiler(this.compiler, this.options);
// 监听compiler.hooks.done line 182
const {
done } = compiler.hooks;
done.tap('webpack-dev-server', (stats) => {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
// 实例化express line 168
this.app = new express();
// 调用webpack-dev-middleware line 207
webpackDevMiddleware(