webpack之HMR

什么是HMR

  • Hot Module Replacement是指当我们对代码修改并保存后,webpack将会对代码进行重新打包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,以实现在不刷新浏览器的前提下更新页面

使用HMR

安装

yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin socket.io socket.io-client events mime fs-extra --dev

使用

webpack.config.js

webpack.config.js

let path = require("path");
let webpack = require("webpack");
let HtmlWebpackPlugin = require("html-webpack-plugin");
let HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = {mode: "development",entry:"./src/index.js",output: {filename: "[name].js",path: path.resolve(__dirname, "dist")},devServer:{hot:true,port:8000,contentBase:path.join(__dirname,'static')},plugins: [new HtmlWebpackPlugin({template:'./src/index.html'}),new HotModuleReplacementPlugin()]
}

src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>hmr</title>
</head>
<body><input/><div id="root"></div>
</body>
</html>

src\index.js

src\index.js

let render = () => {let title = require("./title.js");root.innerText = title;
}
render();if (module.hot) {module.hot.accept(["./title.js"], render);
}

title.js

src\title.js

module.exports = "title";

package.json

"scripts": {"build": "webpack","dev": "webpack serve"
}

debugger

  "scripts": {"build": "webpack","dev": "webpack serve",
+    "debug": "webpack serve"},

基础知识

module和chunk

  • 在 webpack里有各种各样的模块
  • 一般一个入口会依赖多个模块
  • 一个入口一般会对应一个chunk,这个chunk里包含这个入口依赖的所有的模块

HotModuleReplacementPlugin

  • webpack\lib\HotModuleReplacementPlugin.js
  • 它会生成两个补丁文件
    • 上一次编译生成的hash.hot-update.json,说明从上次编译到现在哪些代码块发生成改变
    • chunk名字.上一次编译生成的hash.hot-update.js,存放着此代码块最新的模块定义,里面会调用webpackHotUpdate方法
  • 向代码块中注入HMR runtime代码,热更新的主要逻辑,比如拉取代码、执行代码、执行accept回调都是它注入的到chunk中的
  • hotCreateRequire会帮我们给模块 module的parentschildren赋值

webpack的监控模式

  • 如果使用监控模式编译webpack的话,如果文件系统中有文件发生了改变,webpack会监听到并重新打包
  • 每次编译会产生一个新的hash值

工作流程

服务器部分

  1. 启动webpack-dev-server服务器
  2. 创建webpack实例
  3. 创建Server服务器
  4. 添加webpack的done事件回调,在编译完成后会向浏览器发送消息
  5. 创建express应用app
  6. 使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
  7. 设置文件系统为内存文件系统
  8. 添加webpack-dev-middleware中间件
  9. 创建http服务器并启动服务
  10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些socket消息进行不同的操作。当然服务端传递的最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换
步骤代码位置
1.启动webpack-dev-server服务器webpack-dev-server.js#L159
2.创建webpack实例webpack-dev-server.js#L89
3.创建Server服务器webpack-dev-server.js#L100
4.更改config的entry属性webpack-dev-server.js#L157
entry添加dev-server/client/index.jsaddEntries.js#L22
entry添加webpack/hot/dev-server.jsaddEntries.js#L30
5. setupHooksServer.js#L122
6. 添加webpack的done事件回调Server.js#L183
编译完成向websocket客户端推送消息,最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换Server.js#L178
7.创建express应用appServer.js#L169
8. 添加webpack-dev-middleware中间件Server.js#L208
以watch模式启动webpack编译,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包index.js#L41
设置文件系统为内存文件系统index.js#L65
返回一个中间件,负责返回生成的文件middleware.js#L20
app中使用webpack-dev-middlerware返回的中间件Server.js#L128
9. 创建http服务器并启动服务Server.js#L135
10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接Server.js#L745
创建socket服务器并监听connection事件SockJSServer.js#L33

客户端部分

  1. webpack-dev-server/client-src/default/index.js端会监听到此hash消息,会保存此hash值
  2. 客户端收到ok的消息后会执行reloadApp方法进行更新
  3. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器
  4. webpack/hot/dev-server.js会监听webpackHotUpdate事件,然后执行check()方法进行检查
  5. 在check方法里会调用module.hot.check方法
  6. 它通过调用 JsonpMainTemplate.runtimehotDownloadManifest方法,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件,该 Manifest 包含了所有要更新的模块的 hash 值和chunk名
  7. 调用JsonpMainTemplate.runtimehotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码
  8. 补丁JS取回来后会调用JsonpMainTemplate.runtime.jswebpackHotUpdate方法,里面会调用hotAddUpdateChunk方法,用新的模块替换掉旧的模块
  9. 然后会调用HotModuleReplacement.runtime.jshotAddUpdateChunk方法动态更新模块代 码
  10. 然后调用hotApply方法进行热更新
步骤代码
1.连接websocket服务器socket.js#L25
2.websocket客户端监听事件socket.js#L53
监听hash事件,保存此hash值index.js#L55
3.监听ok事件,执行reloadApp方法进行更新index.js#L93
4. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器reloadApp.js#L7
5. 在webpack/hot/dev-server.js会监听webpackHotUpdate事件dev-server.js#L55
6. 在check方法里会调用module.hot.check方法dev-server.js#L13
7. 调用hotDownloadManifest,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件(lastHash.hot-update.json),该 Manifest 包含了本次编译hash值 和 更新模块的chunk名HotModuleReplacement.runtime.js#L180
8. 调用JsonpMainTemplate.runtimehotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码JsonpMainTemplate.runtime.js#L14
9. 补丁JS取回来后会调用JsonpMainTemplate.runtime.jswebpackHotUpdate方法JsonpMainTemplate.runtime.js#L8
10. 然后会调用HotModuleReplacement.runtime.jshotAddUpdateChunk方法动态更新模块代码HotModuleReplacement.runtime.js#L222
11.然后调用hotApply方法进行热更新HotModuleReplacement.runtime.js#L257 HotModuleReplacement.runtime.js#L278
12.从缓存中删除旧模块HotModuleReplacement.runtime.js#L510
13.执行accept的回调HotModuleReplacement.runtime.js#L569

相关代码

  • webpack-dev-server.js
  • Server.js
  • webpack-dev-middleware/index.js
  • SockJSServer.js

启动开发服务器

startDevServer.js

startDevServer.js

const webpack = require("webpack")
const Server = require('./webpack-dev-server/lib/Server');
const config = require("./webpack.config")
function startDevServer(compiler,options) {const devServerOptions = options.devServer||{};const server = new Server(compiler, devServerOptions);const {host='localhost',port=8080}=devServerOptions;server.listen(port, host, (err) => {console.log(`Project is running at http://${host}:${port}`);});
}
const compiler = webpack(config);
startDevServer(compiler,config);

Server.js

webpack-dev-server\lib\Server.js

const express = require("express");
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;this.setupApp();this.createServer();}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

package.json

package.json

  "scripts": {"build": "webpack","dev": "webpack-dev-server",
+   "start":"node ./startDevServer.js"},

给entry添加客户端

Server.js

webpack-dev-server\lib\server\Server.js

const express = require("express");
+const updateCompiler = require('./utils/updateCompiler');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;
+       updateCompiler(compiler);this.setupApp();this.createServer();}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

updateCompiler.js

webpack-dev-server\lib\utils\updateCompiler.js

const path = require("path");
let updateCompiler = (compiler) => {const config = compiler.options;//来自webpack-dev-server/client/index.js 在浏览器启动WS客户端config.entry.main.import.unshift(require.resolve("../../client/index.js"),);//webpack/hot/dev-server.js 在浏览器监听WS发射出来的webpackHotUpdate事件config.entry.main.import.unshift(require.resolve("../../../webpack/hot/dev-server.js"));console.log(config.entry);compiler.hooks.entryOption.call(config.context, config.entry);
}
module.exports = updateCompiler;

client\index.js

webpack-dev-server\lib\client\index.js

console.log('webpack-dev-server\client\index.js');

dev-server.js

webpack-dev-server\lib\client\hot\dev-server.js

console.log('webpack-dev-server\lib\client\hot\dev-server.js');

添加webpack的done事件回调

Server.js

webpack-dev-server\lib\server\Server.js

const express = require("express");
const updateCompiler = require('./utils/updateCompiler');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions=devServerOptions;updateCompiler(compiler);
+       this.sockets = [];
+       this.setupHooks();this.setupApp();this.createServer();}
+    setupHooks() {
+        this.compiler.hooks.done.tap('webpack-dev-server', (stats) => {
+            console.log("stats.hash", stats.hash);
+            this.sockets.forEach((socket) => {
+                socket.emit("hash", stats.hash);
+                socket.emit("ok");
+            });
+            this._stats = stats;
+        });
+    }setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

webpack-dev-middleware中间件

  • webpack-dev-middleware 实现webpack编译和文件相关操作

Server.js

webpack-dev-server\lib\Server.js

const express = require("express");
const updateCompiler = require('./utils/updateCompiler');
+const webpackDevMiddleware = require('../../webpack-dev-middleware');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;updateCompiler(compiler);this.sockets = [];this.setupHooks();this.setupApp();
+       this.setupDevMiddleware();this.createServer();}
+   setupDevMiddleware() {
+        if(this.devServerOptions.contentBase)
+            this.app.use(express.static(this.devServerOptions.contentBase));
+        this.middleware = webpackDevMiddleware(this.compiler);
+        this.app.use(this.middleware);
+   }setupHooks() {this.compiler.hooks.done.tap('webpack-dev-server', (stats) => {console.log("stats.hash", stats.hash);this.sockets.forEach((socket) => {socket.emit("hash", stats.hash);socket.emit("ok");});this._stats = stats;});}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

webpack-dev-middleware\index.js

webpack-dev-middleware\index.js

const middleware = require("./middleware");
const MemoryFileSystem = require("memory-fs");
let memoryFileSystem = new MemoryFileSystem();
function webpackDevMiddleware(compiler) {compiler.watch({}, () => {console.log("start watching!");});let fs = compiler.outputFileSystem = memoryFileSystem;return middleware({fs,outputPath:compiler.options.output.path});
}module.exports = webpackDevMiddleware;

middleware.js

webpack-dev-middleware\middleware.js

const mime = require('mime');
const path = require("path");
module.exports = function wrapper(context) {return function middleware(req, res, next) {let url = req.url;if (url === "/") { url = "/index.html"; }let filename = path.join(context.outputPath, url);try {let stat = context.fs.statSync(filename);if (stat.isFile()) {let content = context.fs.readFileSync(filename);res.setHeader("Content-Type", mime.getType(filename));res.send(content);} else {res.sendStatus(404);}} catch (error) {res.sendStatus(404);}};
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/27076.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fluid 1.0 版发布,打通云原生高效数据使用的“最后一公里”

作者&#xff1a;顾荣 前言 得益于云原生技术在资源成本集约、部署运维便捷、算力弹性灵活方面的优势&#xff0c;越来越多企业和开发者将数据密集型应用&#xff0c;特别是 AI 和大数据领域应用&#xff0c;运行于云原生环境中。然而&#xff0c;云原生计算与存储分离架构虽…

easyexcel的简单使用(execl模板导出)

模板支持功能点 支持列表支持自定义头名称支持自定义fileName支持汇总 模板示例 操作 pom引入 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>${easyexcel.version}</version></dep…

uniapp中u-input点击事件失效

当给u-input设置了disabled/readonly属性后&#xff0c;pc浏览器中点击事件失效&#xff0c;但是app/移动端h5中却仍有效 解决办法 给外边包上一个盒子设置点击事件&#xff0c;给input加上css属性&#xff1a;pointer-events&#xff1a;none pointer-events CSS 属性指定在什…

糖料蔗精准测产 ,珈和科技倾力打造广西农险科技服务新标杆!

2024年中央一号文件中提到&#xff0c;鼓励地方发展特色农产品保险。随着近年来广西省对农业风险管理方式的不断探索与试点&#xff0c;糖料蔗、桉树、柑橘等种植收入保险需求迅速增加&#xff0c;传统的测产方式在效率上不仅耗时费力&#xff0c;而且难以满足大规模经济作物的…

Vue笔记(三)

上一篇&#xff1a;Vue二&#xff09;-CSDN博客 目录 1.自定义指令 v-loading的封装 2.插槽 文本插槽 文本插槽&#xff08;有默认值&#xff09; 具名插槽 作用域插槽 详细做一个练习 实现如下效果 目录结构 准备数据 父传子数据 使用文本插槽自定义按钮文本 实…

RushJs遇到Browserslist: caniuse-lite is outdated解决方案

突然。 CI服务器上打包应用的是会报一个警告。 Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-dblatest Why you should do it regularly: https://github.com/browserslist/update-db#readmeBrowserslist里的数据库过期了。 这个警告会让C…

Maven: 编码GBK的不可映射字符不能编译

使用mvn compile命令&#xff0c;出现错误: 编码GBK的不可映射字符不能编译。这是因为代码或注释中存在中文引起的&#xff0c;一般在ide中会自动处理编译时的字符集&#xff0c;就不会碰到这个错误。这个错误是在生成代码后&#xff0c;其中自动加上了中 文注释&#xff0c;手…

vue路由:hash模式下跳转404的问题。

vue的路由模式&#xff1a;hash和history。 最近有个问题&#xff0c;其实很常见就是hash模式跳转404。 发生问题的场景&#xff1a; 1.项目加载的动态路由。 2.首页和登录页在一个动态路由模块&#xff0c;而其他的管理层页面模块在另外一个动态路由模块。按需加载&#x…

在两个脚本之间传输文本信息

一、接收方有一个文本&#xff0c;用于显示接收的信息 (一&#xff09;接收方&#xff1a; 1、UI-Text TextMeshPro&#xff0c;名字自取。如&#xff1a;TitleText 2、给TitleText添加Recipients.cs组件。 using System.Collections; using System.Collections.Generic; u…

动态防护开启教程和体验感受

动态防护是雷池 WAF 社区版在版本 [6.0.0] 中新增的一个功能&#xff0c;它属于站点高级防护的一部分。动态防护的主要作用是自动动态加密网站的 HTML 和 JavaScript 源码&#xff0c;目的是阻止爬虫和攻击自动化程序的分析。这项功能在 [6.0.0] 版本中标记为 BETA 版本&#x…

c#中switch case语句的用法

前言 在c#中如果对于两种不同情况的处理&#xff0c;一般使用If else结构&#xff0c;但是对于3种及以上情况的处理最好使用switch case语句来增强代码的可读性&#xff0c;本文就是详细介绍switch case语句在c#中的常见应用 1、常规使用 下面的代码中c是一个Int类型的变量&…

2024 年最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)

OpenAi 环境安装 首先确保您的计算机上已经安装了 Python。您可以从 Python 官方网站下载并安装最新版本 Python。安装时&#xff0c;请确保勾选 “Add Python to PATH” &#xff08;添加环境变量&#xff09;选项&#xff0c;以便在 cmd 命令行中直接使用 Python。 安装 Op…

华为大咖说 | AI 是行业的未来, 还是另一个“元宇宙”?

本文作者&#xff1a;陈冠宏&#xff08;华为网络MSSD首席顾问&#xff09;全文约4497字&#xff0c;阅读约需10分钟 在本年度的517电信日上&#xff0c;中国电信高层在产品升级计划发布会中喊出“ALL in AI”战略&#xff0c;其震撼力让人瞩目。 自2022年11月OpenAI推出划时代…

机器学习归一化特征编码

特征缩放 因为对于大多数的机器学习算法和优化算法来说&#xff0c;将特征值缩放到相同区间可以使得获取性能更好的模型。就梯度下降算法而言&#xff0c;例如有两个不同的特征&#xff0c;第一个特征的取值范围为1——10&#xff0c;第二个特征的取值范围为1——10000。在梯度…

Facebook隐私保护:用户数据安全的挑战与应对策略

在当今数字化时代&#xff0c;随着社交媒体的普及和信息技术的快速发展&#xff0c;人们对于个人数据隐私和安全的关注越来越高。作为全球最大的社交网络平台之一&#xff0c;Facebook在用户数据保护方面面临着诸多挑战和责任。本文将深入探讨Facebook在隐私保护方面的现状、面…

k8s+springcloud+nacos部署配置

1 k8s 部署nacos-2.1.2配置k8s-nacos-statefulSet.yaml文件 apiVersion: v1 kind: Service metadata:name: nacos-headlessnamespace: rz-dtlabels:app: nacosannotations:service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" spec:# 3个端口打开&…

拓扑排序和关键路径详解

目录 拓扑排序 关键路径 拓扑排序 如果有一个有向图的任意顶点都无法通过一些有向边回到身边&#xff0c;那么称这个有向图为有向无环图。 拓扑排序是将有向无环图的所有顶点排成一个线性序列&#xff0c;使得对图中的任意两个顶点u,v,如果存在边u->v&#xff0c;那么在…

VMware ESXi 8.0U2c macOS Unlocker OEM BIOS ConnectX-3 网卡定制版 (集成驱动版)

VMware ESXi 8.0U2c macOS Unlocker & OEM BIOS ConnectX-3 网卡定制版 (集成驱动版) 发布 ESXi 8.0U2 集成驱动版&#xff0c;在个人电脑上运行企业级工作负载 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-8-u2-sysin/&#xff0c;查看最新版。原创作…

UE5.2打包安卓

目录 简介: 一. 根据官网配置 二. 手动定位SDK路径 三: 设置Android基本信息 四: 设置KeyStore 五: 开始打包 六:其他 七. 总结 简介: UE5.2 打包安卓是指将使用 Unreal Engine 5.2 开发的项目编译为可在安卓设备上运行的安装包。 以下是一般的打包步骤&#xff1a; 安装…

centos7 xtrabackup mysql 基本测试(3)---虚拟机环境 安装mysql

centos7 xtrabackup mysql 基本测试&#xff08;3&#xff09;—虚拟机环境 安装mysql centos7 安装 mysql5.7 可以在运行安装程序之前导入密钥&#xff1a; sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022第一步、下载MySQL 安装包&#xff1a; sudo w…