webpack之HMR(Hot Module Replacement)
HMR
什么是HMR?
Hot Module Replacement(HMR),当你对代码进行修改并保存后,webpack将对代码重新打包,并将模块发送到浏览器端,浏览器通过新的模块替代老的模块,
这样在不刷新浏览器的前提下就能够对应用进行更新。HMR-模块热替换功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
HMR是如何工作的?
- 修改了一个或多个文件
- 文件系统接受更改并通知webpack
- webpack重新编译构建一个或多个模块,并通知HMR服务器进行了更新
- HMR Server使用websockets通知HMR Runtime需要更新(HMR运行时通过HTTP请求这些更新)
- HMR运行时在替换更新中的模块。如果确定这些模块无法更新,则触发整个页面刷新
HMR工作流程图解
上图是webpack配合webpack-dev-server进行应用开发的模块热更新流程图
- 上图底部红色框内是webpack代码控制的区域。蓝色方框是webpack-dev-server代码控制的区域,洋红色的方框是文件系统,文件修改后的变化就发生
在这,而青色的方框是应用本身,步骤分析 - 第一步,在webpack的watch模式下,webpack系统中摸一个文件发生修改,webpack监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的
JavaScript对象保存自内存中 - 第二步是wbepack-dev-server和webpack之间的接口交互,而在这一步,主要是dev-server的中间件webpack-dev-middleware和webpack之间的交互,webpack-dev-middleware调用webpack暴露的API对代码
变化进行监控,并告诉webpack,将代码打包到内存中 - 第三步是webpack-dev-server对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase为true的时候Server会监听这些配置文件中静态文件的变化,变化后
会通知浏览器端对应用进行live reload。注意,这儿是浏览器刷新,和HMR是两个概念。 - 第四步也是webpack-dev-server代码的工作,该步骤主要是通过sockjs(webpack-dev-server的依赖)在浏览器端和服务端之间建立一个websocket长链接,将webpack编译打包的各个阶段的状态信息告知浏览器端,
同时也包括第三步中Server监听静态文件变化系信息。浏览器端根据这些socket消息进行不同的操作,当然服务端传递的最主要信息还是新模块的hash值。后面的步骤根据这一hash值来进行模块热替换 - webpack-dev-server/client端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交给了webpack,webpack/hot/dev-server的工作就是根据webpack-dev-server/client传给他的信息
以及dev-server的配置剧的定式刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了、 - HotModuleReplacement.runtime是客户端HMR的中枢,它接收到上一步传递给他的新模块的hash值,它通过JsonpMainTemplate.runtime向
server端发送Ajax请求,服务端返回一个json,该json包含了所有要更新的模块的hash值,获取到更新列表后,该模块再次通过jsonp请求,获取到最新的模块代码。这就是7/8/9步骤 - 而弟10步是决定HMR成功与否的关键步骤,在该步骤中,HotModulePlugin将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块
的同事更新模块间的以来引用 - 最后一步,当HMMR失败后,回退到live reload操作,也就是惊醒浏览器刷新来获取最新打包代码
HMR和livereload区别?
livereload是一个web开发辅助工具,当我们修改完html、css和js的时候会自动刷新浏览器,不需要在F5了。那么liveReload和HMR有什么区别呢,liveReload是实时刷新整个页面,而HMR是更新已更改的模块,而无需重新加载页面
NamedModulesPlugin
new webpack.NameModulesPlugin(),
当开启HMR的时候使用该插件会显示模块的相对路径,建议用于开发环境
HotModuleReplacementPlugin
该插件的作用就是实现模块热替换,实际上当启动时带上--hot
参数,会注入改插件
new webpack.HotModuleReplacementPlugin()
运行HMR的简单例子
- 文件目录如下
- hello.js内容如下
const hello = () => "hello world";
//const hello = () => "hello eleme";
export default{
hello
}
- index.js内容如下
import hello from "./hello.js";
import "./style.css"
function component(){
var element = document.createElement("div");
element.innerHTML = hello.hello();
return element
}
console.log(hello.hello())
document.body.appendChild(component());
console.log(module)
if(module.hot){
module.hot.accept("./hello.js",function(){
console.log("accepting the updated printMe module",hello.hello());
})
}
- style.css内容如下
body{
/*background-color: blue;*/
background-color: black;
color: #fff;
}
- webpack.hmr.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
module.exports = {
entry:path.resolve(__dirname,"src/index.js"),
output:{
filename:"bundle.js",
path:path.resolve(__dirname,"dist")
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:"HMR"
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devtool:"inline-source-map",
devServer:{
contentBase:"./dist",
hot:true,
inline:true
}
}
- package.json
{
"name": "webpackDevServer",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config demo/webpack-dev-server/webpack-dev-server.js",
"server": "node demo/webpack-dev-middleware/server.js",
"hmr": "webpack-dev-server --config demo/HMR/webpack.hmr.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "3.0.0",
"css-loader": "3.2.0",
"express": "4.17.1",
"html-webpack-plugin": "3.2.0",
"style-loader": "1.0.0",
"webpack": "4.41.2",
"webpack-cli": "3.3.9",
"webpack-dev-middleware": "3.7.2",
"webpack-dev-server": "3.8.2"
}
}
- 执行npm run hmr
结果如下
会看到,在改变hello.js内容的时候,不用刷新,页面也自动更改,修改style.css内容,页面背景颜色也跟着在改变