Hot Module Replacement 热模块更新
文章目录
热模块替换,通常简写为hmr,能够在不刷新页面、不影响其他模块功能的情况下,热更新你本次修改的内容。
本文将通过两个个简单的案例介绍一下hmr
1. Hot Module Replacement的css案例
先根据以下提供的几个配置文件搭建一下项目
package.json
{
"name": "practice1",
"version": "1.0.0",
"description": "cousinChen的demo",
"dependencies": {},
"devDependencies": {
"autoprefixer": "^9.3.1",
"clean-webpack-plugin": "^1.0.0",
"css-loader": "^3.6.0",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"node-sass": "^4.10.0",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.11.1"
},
"scripts": {
"start": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC"
}
webpack.config.js
//引入node的核心模块 path
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
/**
* commonJs语法
*/
module.exports = {
//指定项目的模式 production:生产环境 development:开发环境
mode: 'development',
devtool: 'source-map',
// devtool: 'none',
//指定入口文件,从哪个文件开始打包
//entry对象可以简写为如下形式
//entry: './src/index.js',
entry: {
main: './src/index.js',
},
//使用webpackDevServer为我们实现服务端的功能
devServer: {
//指定服务端的路径
contentBase: './dist',
//open为true,项目启动时会自动打开浏览器访问项目
open: true,
port: 8080,
proxy: {
'/api': 'http://localhost:8081'
}
},
module: {
rules: [{
//正则匹配文件后缀
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
//通过使用Placeholders(占位符)的方式,指定打包后的文件名使用原文件名,更多具体用法参考官网file-loader的placeholder用法
name: '[name].[ext]',
//指定打包输出路径
outputPath: 'images/',
//当文件小于20KB时,直接将文件以Base64编码的形式打包到js文件中,而不是生成到outputPath指定的文件夹中。
limit: 1024 * 20
}
}
}, {
//正则匹配scss文件后缀
test: /\.scss$/,
//引入处理css、sass、自动补全浏览器兼容属性的loader
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
// modules: true
}
},
'sass-loader',
'postcss-loader'
]
}, {
//正则匹配css文件后缀
test: /\.css$/,
//引入处理css、sass、自动补全浏览器兼容属性的loader
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},{
//正则匹配文件后缀
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'])
],
//打包后的文件和其输出目录
output: {
filename: '[name].js',
//调用path模块的resolve方法
//node中_dirname这个变量,实际指的就是webpack.config.js这个文件所在目录的路径。
//传入的'bundle',最终拼接出来的路径就是打包后输出的目录
//如果不配置path, webpack会默认在我们根目录下创建一个名为dist的目录作为打包输出目录
path: path.resolve(__dirname, 'dist')
}
}
在src目录下的index.html中,只写一个id为root的div。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack学习</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
在src目录下,创建style.css文件,编写如下样式
div:nth-of-type(odd) {
background: red;
}
打开src目录下的index.js,编写如下代码,实现功能:点击新增按钮,在网页上生成内容为item的div节点
import './style.css';
var btn = document.createElement('button');
btn.innerHTML = '新增';
document.body.appendChild(btn);
btn.onclick = function(){
var div = document.createElement('div');
div.innerHTML = 'item';
document.body.appendChild(div);
}
点击新增按钮,在页面上生成了颜色为红色的div
当我们将style.css文件中背景色改为蓝色,再查看浏览器,发现webpackDevServer自动编译后,页面重新加载,导致之前渲染的dom节点、样式都失效了。
div:nth-of-type(odd) {
background: blue;
}
那么,能不能在我们修改样式后,页面不但不自动加载,还能应用修改后的样式呢?当然可以,使用hmr就行!
2. Hot Module Replacement的配置
如下修改webpack.confg.js文件
- 在devServer中添加hot和hotOnly两项配置
- 在webpack.confg.js文件头部引入webpack模块
- 在plugins中引入webpack热模块更新插件,webpack.HotModuleReplacementPlugin()
//引入node的核心模块 path
const webpack = require('webpack');
module.exports = {
mode: '',
devtool: '',,
entry: {},
devServer: {
contentBase: './dist',
open: true,
port: 8080,
proxy: {
'/api': 'http://localhost:8081'
},
//让webpackDevServer开启hot module replacement功能
hot: true,
//当hot module replacement功能没生效时,也不允许浏览器重新加载
hotOnly: true
},
module: {},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
//引入webpack热模块更新插件
new webpack.HotModuleReplacementPlugin()
],
output: {}
}
在控制台输入npm run start
重启项目,在浏览器页面点击新增按钮,生成蓝色的div
然后去style.css中修改颜色为红色,自动编译完后发现页面并没有重新加载,且蓝色的div变成了红色,说明hmr生效了。
div:nth-of-type(odd) {
background: blue;
}
3. Hot Module Replacement的js案例
将上面css案例中的热模块功能关闭,我们演示一下开关热模块功能对js模块代码的影响。
在src目录下,创建counter.js文件,作为一个模块供外部调用。实现功能:在页面上生成一个数字,每次点击这个数字,数字累计加一。
function counter(){
var div = document.createElement('div');
div.setAttribute('id', 'counter');
div.innerHTML = 1;
div.onclick = function(){
div.innerHtml = parseInt(div.innerHTML, 10) + 1;
}
document.body.appendChild(div);
}
export default counter;
再创建一个number.js文件,同样作为一个模块供外部调用。实现功能:在页面显示数字1000。
function number(){
var div = document.createElement('div');
div.setAttribute('id', 'number');
div.innerHTML = 1000;
document.body.appendChild(div);
}
export default number;
在index.js中引入counter、number模块,并调用counter()、number()方法。
import counter from './counter';
import number from './number';
counter();
number();
此时没有开启Hot Module Replacement之前,即没有配置devServer下的hot,也没有引入引入webpack热模块更新插件。
点击第一个数字,让它累加(counter模块的功能)
然后修改number.js文件中的内容,将div.innerHTML值改成2000。(即这边修改的是number模块的代码)
此时页面因为重新加载,导致counter模块累加的数字被重置为1了
而在开启Hot Module Replacement之后,点击第一个数字进行累加为9,修改number.js文件中的数值为3000,却发现热更新成功后,页面确实没有重新加载,第一个数字也没被重置为1,但是,2000也没有变为3000呀!这似乎和预想的不太一样。别急,这是因为js的热模块更新和css的有些区别。
在index.js中加入以下代码,因为js模块热更新后,需要我们自己决定具体哪些模块需要热更新。
//判断模块是否热更新
if(module.hot){
//当检测到number模块热更新后,执行以下逻辑
module.hot.accept('./number', ()=>{
//移除页面上的number节点
document.body.removeChild(document.getElementById('number'));
//重新调用number()方法
number();
});
}
而上面css文件的修改能及时生效,是因为css的热更新逻辑已经被css-loader实现了,现在知道为什么了吧~