背景
在我们平常使用的vue和react框架时,会遇到某个模块改变了,但是只刷新改变的那个模块,其他模块不会跟着刷新,浏览器也不会刷新。这里就是热模块更新。
什么是?
一块内容改变了,不用刷新浏览器,改变的那块内容就能刷新到最新值。
css热模块HMR
问题,丢失操作痕迹
现在我想改变css里面的一个样式值,但是它会抹掉我之前的操作痕迹,这不是我想要的,有什么办法可以解决呢?
栗子如下:
index.js:
import './css/index.css';
const button=document.createElement('button')
button.innerHTML='新增'
document.body.appendChild(button)
button.onclick=()=>{
const item=document.createElement('p')
item.innerHTML='item'
document.body.appendChild(item)
}
新建一个index.js文件,在页面上去添加一个新增的按钮,然后点击按钮时,每点击一次就会新增一个P元素。文件里引入了一个css样式文件。
css/index.css
p:nth-of-type(odd){
background:skyblue;
}
css中设置了页面中p元素的奇数行背景颜色为天空蓝,我先点击N次按钮添加了N个P元素,现在我想把背景色给成黄色,然后就出现了一下问题:
我改变了P元素的背景色,改变后,浏览器自动刷新,我之前添加的P元素都不见了,又要重新添加,这不是我想要的,我想让颜色改变后,我之前的操作痕迹保留我添加的P元素都还在。
解决
我们开启热模块替换特性,并且设置热模块替换的插件,在webpack中去设置;
首选开启hot:
webpack.config.js:
然后webpack中引用webpack,最后再配置webpack中的HotModuleReplacementPlugin:
webpack.config.js:
webpack.js中的全部代码:
const path =require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports={
entry:{
index: './src/index.js',
},
output:{
path:path.resolve(__dirname,'./dist'),
filename:'[name].js'
},
mode:'development',
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// {
// test: /\.less$/,
// use: [
// MiniCssExtractPlugin.loader,
// "css-loader",
// "postcss-loader",
// "less-loader",
// ],
// },
]
},
devServer: {
contentBase: "./dist",
port: 8001,
open: true,
hot: true, //开启热模块替换
// hotOnly:true
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
chunks:['index']
}),
new MiniCssExtractPlugin({
filename:'css/[name].css'
}),
new webpack.HotModuleReplacementPlugin()//热模块替换的插件
]
}
看效果
最后重新启动serve:
npm run serve
为什么有这样的命令,因为我在package.json中这样配置了:
我们看效果吧:
改变css的颜色值时,对应的颜色都发生了改变,但是我之前添加的p元素都还在,这是因为浏览器没有刷新,只是变化的模块进行了刷新。
以上是css改变时,在webpack中去开启热更新模块就可以了,如果js改变了,会依旧生效吗?
js热模块HMR
index.js:
const counter= () => {
const div = document.createElement('div');
div.setAttribute('id', 'counter');
let a = 0;
div.innerHTML= a ;
div.onclick = () => {
a++;
div.innerHTML = a;
}
document.body.appendChild(div);
}
const number = () => {
const div = document.createElement('div');
div.setAttribute('id', 'number');
div.innerHTML = '18000';
document.body.appendChild(div);
}
counter();
number();
以上代码是:
定义两个函数,其中一个函数counter里面去给页面添加一个div,并给此div添加一个id属性,声明一个变量a初始值为0,div的元素内容设置为变量a,给div添加一个点击事件,点击时,a变量+1,并把a变量再次赋值给div的元素内容。
另外一个函数number, 也是向页面去添加一个div元素,div元素也增加一个id属性number,div元素内容为:18000。
效果如下:
问题,css那套不管用,还是会丢失操作痕迹
如果我改一下number函数里面定义的div的元素值,增加的计数器值还会保留痕迹吗?
可以看到,添加计数器数字增加到13,我们改变js中的div元素值,从18000改成13000,计数器数字成了初始值0,又把13000改成18000,又回到初始值,操作痕迹还是不见了,看来,webapck中之开启热模块更新对于js没啥用。
解决
配置不让浏览器自动刷新
那我们不让浏览器自动刷新,那是不是之前那的操作痕迹就可以保留呢。
webpack中有个hotOnly设置为true就可以禁止浏览器自动刷新。
webpack.config.js:
devServer: {
contentBase: "./dist",
port: 8001,
open: true,
hot: true, //开启热模块替换
hotOnly: true //即便HMR不⽣效,浏览器也不⾃动刷新,就开启hotOnly
},
设置完,看效果:
出现新问题,新的值没有更新
以上可以看到,让浏览器不自动刷新,虽然之前增加计数器的值保留了,但是下面的div元素值我从18000改成13000还是16000,页面值依旧是18000,只有手动刷新浏览器,才会生效,但是计数器的值又回到了初始值,操作痕迹还是丢失了。
观察模块更新,从而更新
使用module.hot.accept来观察模块更新,从而更新
我们把之前的测试代码分别提取到两个新建的a,b,js文件中,如下:
a.js:
const counter = () => {
const div = document.createElement('div');
div.setAttribute('id', 'counter');
let a = 0;
div.innerHTML = a;
div.onclick = () => {
a++;
div.innerHTML = a;
}
document.body.appendChild(div);
}
export default counter;
b.js:
const number = () => {
const div = document.createElement('div');
div.setAttribute('id', 'number');
div.innerHTML = '17000';
document.body.appendChild(div);
}
export default number;
目录如下:
然后,在入口文件index.js中这样处理,引入a.js和b.js,再调用里面的counter和number方法去运行。
接着开始上module.hot.accept,首先判断是否有module.hot,有的话再用module.hot.accept去观察b.js是否有变化,如果变了,就用查找id属性的方式查找出元素,去移除它,再去添加变化元素的新值,也就是再次调用一下number方法就行。
具体代码如下:
import './css/index.css'
import counter from './js/a'
import number from './js/b'
// const button=document.createElement('button')
// button.innerHTML='新增'
// document.body.appendChild(button)
// button.onclick=()=>{
// const item=document.createElement('p')
// item.innerHTML='item'
// document.body.appendChild(item)
// }
counter();
number();
if(module.hot){
module.hot.accept('./js/b.js',function(){
document.body.removeChild(document.getElementById('number'));
number();
})
}
最后看下效果:
可以看到,我们中途修改js值,之前的操作痕迹也并没有丢失。
总结
以上就是HMR热模块更新啦:
css值修改的话,只需要在Webpack中去开启hot:true并且使用webpack.HotModuleReplacementPlugin()热模块替换插件就可以啦;
js值修改的话,就是用module.hot.accept观察模块更新,哪个模块变化了,就利用每个模块自身唯一id属性值去查找该模块变化的元素,然后先删除改变之前的元素,再重新添加变化后的元素。这里给每个模块添加唯一的id属性很重要,是用来这里查找该模块元素的。