Webpack热更新(
Hot Module Replacement
,简称HMR
),无需完全刷新整个页面的同时,更新所有类型的模块,是 Webpack 提供的最有用的功能之一。
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中对
CSS / JS
进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
HMR作为一个Webpack内置的功能,可以通过
HotModuleReplacementPlugin
或--hot
开启。
刷新我们一般分为两种:
- 一种是页面刷新,不保留页面状态,就是简单粗暴,直接
window.location.reload()
。 - 另一种是基于
WDS (Webpack-dev-server)
的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留当前的页面状态,比如复选框的选中状态、输入框的输入等。
webpack 构建
项目启动之后,会进行首次构建打包,控制台中会输出整个的构建过程,可以观察到一个 Hash 值 3606e1ab1ddcf6626797。
在每次代码的修改后,保存时都会在控制台上出现 compiling…字样,可以在控制台中观察到:
- Hash 值更新:4f8c0eff7ac051c13277;
- 新生成文件:3606e1ab1ddcf6626797.hot-update.json
- main1.3606e1ab1ddcf6626797.hot-update.js。
如果没有任何改动,直接保存,控制台输出编译打包信息,Hash 值没有发生变化,仍旧是 4f8c0eff7ac051c13277。
再次修改代码保存,控制台输出编译打包信息。根据文件名可以发现,上次输出的 Hash 值被作为本次编译新生成的 Hmr 文件标识。同样,本次输出的 Hash 值会被作为下次热更新的标识。
webpack watch
为什么代码的改动保存会自动编译,重新打包?这一系列的重新检测编译依赖于 Webpack 的文件监听:在项目启动之后,
Webpack
会通过Compiler
类的Run
方法开启编译构建过程,编译完成后,调用Watch
方法监听文件变更,当文件发生变化,重新编译,编译完成之后继续监听。
页面的访问需要依赖 Web 服务器,那要如何将 Webpack 编译打包之后的文件传递给 Web 服务器呢?这就要看 Webpack-dev-middleware了
。 Webpack-dev-middleware 是一个封装器( wrapper )
,它可以把 Webpack 处理过的文件发送到一个Server
(其中 Webpack-Dev-Server
就是内置了 Webpack-dev-middleware
和 Express 服务器
)。上面有说到编译之后的文件会被写入到内存,而 Webpack-dev-middleware
插件通过 memory-fs
实现静态资源请求直接访问内存文件。
const webpack = require('webpack');
const webpackConfig = require('./webpack.dev.conf');
const compiler = webpack(webpackConfig);
debug('webpack编译完成');
debug('将编译流通过webpack-dev-middleware');
const devMiddleware = require('webpack-dev-middleware')(compiler, {
// self-define options
});
上面代码可以看到,Webpack 编译打包之后得到一个 Compilation
,并将 Compilation
传递到 Webpack-dev-middleware
插件中,Webpack-dev-middleware
可以通过 Compilation
调用 Webpack
中 的 Watch
方法实时监控文件变化,并重新编译打包写入内存。
留意一下浏览器端,在 Network 中可以看到几个请求:
/__Webpack_hmr 请求返回的消息包含了首次 Hash 值,每次代码变动重新编译后,浏览器会发出 hash.hot-update.json、fileChunk.hash.hot-update.js 资源请求。
点开查看 hash.hot-update.json 请求,返回的结果中,h 是一个 hash 值,用于下次文件热更新请求的前缀,c 表示当前要热更新的文件是 main1 。
继续查看 fileChunk.hash.hot-update.js,返回的内容是使用 webpackHotUpdate 标识的 fileChunk 内容。
热更新源码
我们根据webpack-dev-server
的package.json
中的bin
命令,可以找到命令的入口文件bin/webpack-dev-server.js
。
// node_modules/webpack-dev-server/bin/webpack-dev-server.js
// 生成webpack编译主引擎 compiler
let compiler = webpack(config);
// 启动本地服务
let server = new Server(compiler, options, log);
server.listen(options.port, options.host, (err) => {
if (err) {throw err};
});
本地服务代码:
// node_modules/webpack-dev-server/lib/Server.js
class Server {
constructor() {
this.setupApp();
this.createServer();
}
setupApp() {
// 依赖了express
this.app = new express();
}
createServer() {
this.listeningApp = http.createServer(this.app);
}
listen(port, hostname, fn) {
return this.listeningApp.listen(port, hostname, (err) => {
// 启动express服务后,启动websocket服务
this.createSocketServer();
}
}
}
总结:
这一小节代码主要做了三件事:
- 启动
webpack
,生成compiler
实例。compiler
上有很多方法,比如可以启动webpack
所有编译工作,以及监听本地文件的变化。 - 使用
express
框架启动本地server`,让浏览器可以请求本地的静态资源。 - 本地
server
启动之后,再去启动websocket
服务,通过websocket
,可以建立本地服务和浏览器的双向通信。这样就可以实现当本地文件发生变化,立马告知浏览器可以热更新代码啦!
如果不了解websocket
,建议简单了解一下websocket速成.
参考文章
- 轻松理解webpack热更新原理
- 看完这篇,面试再也不怕被问 Webpack 热更新