hmr webpack 不编译_Webpack HMR 热更新实现原理深入分析

article/2025/10/7 12:25:56

概述

在使用 Webpack 构建开发期时,Webpack 提供热更新功能为开发带来良好的体验和开发效率,那热更新机制是怎么实现的呢?

代码实现

Webpack 配置添加 HotModuleReplacementPlugin 插件

new webpack.HotModuleReplacementPlugin({

// Options...

})

Node Server 引入 webpack-dev-middlerware 和 webpack-hot-middleware 插件,如果是 koa 引入对应的 koa-webpack-dev-middlerware 和 koa-webpack-hot-middleware

const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {

publicPath,

stats: {

colors: true,

children: true,

modules: false,

chunks: false,

chunkModules: false,

},

watchOptions: {

ignored: /node_modules/,

}

});

app.use(devMiddleware);

const hotMiddleware = require('koa-webpack-hot-middleware')(compiler, {

log: false,

reload: true

});

app.use(hotMiddleware);

entry 注入热更新代码

webpack-hot-middleware/client?path=http://127.0.0.1:9000/__webpack_hmr&noInfo=false&reload=true&quiet=false

这里注入热更新代码是关键,保证服务端和客户端能够通信。

热更新一探

首先我们启动 egg-vue-webpack-boilerplate 应用,通过 chrome-dev-tool 看看首次打开页面前端和后端通信是如何建立的?

e931e4cd-870d-4512-b9c0-a80afb490fdb.png

e931e4cd-870d-4512-b9c0-a80afb490fdb.png | center

有一个webpack_hmr的请求:http://127.0.0.1:9000/webpack_hmr.

从这张图里面看到几个信息

这里看到内容类型为 eventStream,具体是啥请看之后介绍的 EventSource。

返回类型为message, 内容关键的有个 action: sync 和 hash:73c528ba5b06e7e9ab26, 这个几个信息在后面会用到。 这里的 hash 为 Webpack 初始化的一个hash,在 vendor.js 文件里面可以看到, 每次页面首次加载时,都会重新生成一个。(var hotCurrentHash = “73c528ba5b06e7e9ab26”)

有一个空的message信息,通过观察发现和后面查看代码发现,这个是为了保证后端与客户端通信保持连接,后端隔一段时间会向客户端发送一段信息。

然后修改 about.vue 文件保存后,发现控制台 Webpack 马上重新编译了,UI 无刷新更新了。

1.这时候会发现 Webpack 编译结果多了两个update的文件, 而且文件名包含上面的 hash 信息。

4.73c528ba5b06e7e9ab26.hot-update.js 2.93 kB 4 [emitted] about/about

73c528ba5b06e7e9ab26.hot-update.json 43 bytes [emitted]

2.同时,chrome-dev-tool 请求面板下多了两个请求,其中 hot-update.json 为 ajax请求, hot-update.js 为 GET 请求, 也就是插入 script 链接到文档中的script 请求。

9852c113-8a43-4832-a3a4-57be16de4644.png

9852c113-8a43-4832-a3a4-57be16de4644.png | center

3.页面内容插入了 4.73c528ba5b06e7e9ab26.hot-update.js script文件

7869d6ae-21bf-474c-bdc2-61850e39bccf.png

7869d6ae-21bf-474c-bdc2-61850e39bccf.png | center

4.我们来初步看一下两个文件的内容:

4.73c528ba5b06e7e9ab26.hot-update.js

{"h":"540f0a679c8bcbf12848","c":{"4":true}}

73c528ba5b06e7e9ab26.hot-update.json

webpackHotUpdate(4,{

(function(module, __webpack_exports__, __webpack_require__){

// ...... 此处为 about.vue 组件代码逻辑

/* hot reload */

if (true) {(function (){

var hotAPI = __webpack_require__(1)

hotAPI.install(__webpack_require__(0), false)

if (!hotAPI.compatible) return

module.hot.accept()

if (!module.hot.data) {

hotAPI.createRecord("data-v-80abbab2", Component.options)

} else {

hotAPI.reload("data-v-80abbab2", Component.options)

' + ' }

module.hot.dispose(function (data){

disposed = true

})

})()}

})

})

5.进行多次热更新效果

33cd6ab3-3784-4284-94e3-56e09063bde9.png

33cd6ab3-3784-4284-94e3-56e09063bde9.png | center

从上面截图可以看到,每次服务端发送的消息(EventStrean) 的 hash 将作为下次 hot-update.json 和 hot-update.js 文件的 hash。

结合上面的分析,接下来从实现到代码层面分析一下整个流程。

热更新实现分析

EventSource 服务端与客户端通信

首先通过查看代码 webpack-hot-middleware/client 发现通信是用 window.EventSource 实现,那 EventSource 是什么东西呢?

EventSource 是 HTML5 中 Server-sent Events 规范的一种技术实现。EventSource 接口用于接收服务器发送的事件。它通过HTTP连接到一个服务器,以text/event-stream 格式接收事件, 不关闭连接。通过 EventSource 服务端可以主动给客户端发现消息,使用的是 HTTP协议,单项通信,只能服务器向浏览器发送; 与 WebSocket 相比轻量,使用简单,支持断线重连。更多信息参考MDN

Node 端通信实现

创建 createEventStream 流

首先看一下中间件核心代码,主要是向客户端发送消息

compile 发送 编译中 消息给客户端

build 发送 编译完成 消息给客户端

sync 文件修复热更新或者报错会发送该消息

// 初始化 EventStream 发送消息通道

var eventStream = {

handler: function(req, res){

req.socket.setKeepAlive(true);

res.writeHead(200, {

'Access-Control-Allow-Origin': '*',

'Content-Type': 'text/event-stream;charset=utf-8',

'Cache-Control': 'no-cache, no-transform',

'Connection': 'keep-alive',

'X-Accel-Buffering': 'no'

});

res.write('\n');

var id = clientId++;

clients[id] = res;

req.on("close", function(){

delete clients[id];

});

},

publish: function(payload){

everyClient(function(client){

client.write("data: " + JSON.stringify(payload) + "\n\n");

});

}

}

// 根据 Webpack 编译状态 主动发送消息给客户端

function webpackHotMiddleware(compiler, opts){

compiler.plugin("compile", function(){

latestStats = null;

if (opts.log) opts.log("webpack building...");

eventStream.publish({action: "building"});

});

compiler.plugin("done", function(statsResult){

// Keep hold of latest stats so they can be propagated to new clients

latestStats = statsResult;

// 当首次编译完成 和 修改代码重新编译(热更新)完成时发送

publishStats("built", latestStats, eventStream, opts.log);

});

var middleware = function(req, res, next){

if (!pathMatch(req.url, opts.path)) return next();

// 见下面的 handler 实现,中间件通过 `req.socket.setKeepAlive` 开启长链接通道,

eventStream.handler(req, res);

if (latestStats) {

// 服务端向客户端写入数据,sync 表示告诉客户端热更新已经准备好

eventStream.publish({

name: stats.name,

action: "sync",

time: stats.time,

hash: stats.hash,

warnings: stats.warnings || [],

errors: stats.errors || [],

modules: buildModuleMap(stats.modules)

});

}

};

return middleware;

客户端通信实现

服务端通过 EventSource 发送消息给客户端了,我们来看看客户端的通信实现。打开 webpack-hot-middleware/client.js 的代码实现:

var source = new window.EventSource('(http://127.0.0.1:9000/__webpack_hmr)');

source.onopen = handleOnline; // 建立链接

source.onerror = handleDisconnect;

source.onmessage = handleMessage; // 接收服务端消息,然后进行相应处理

Node端会主动发送消息给客户端, 客户端 EventSource 关键代码处理消息代码如下:

function processMessage(obj){

switch(obj.action) {

case "building":

if (options.log) {

console.log(

"[HMR] bundle " + (obj.name ? "'" + obj.name + "' " : "") +

"rebuilding"

);

}

break;

case "built": // 这里没有break,所以 编译完成会执行 build 和 sync 逻辑

if (options.log) {

console.log(

"[HMR] bundle " + (obj.name ? "'" + obj.name + "' " : "") +

"rebuilt in " + obj.time + "ms"

);

}

// fall through

case "sync":

processUpdate(obj.hash, obj.modules, options);

break;

default:

if (customHandler) {

customHandler(obj);

}

}

}

上面 building, built, sync 三种消息于服务端发送的消息对应, 这样就完成了服务端和客户端通信。

因 build 的 action 时, build case 没有 break,所以当修改文件时,编译完成发送 build 消息时,会依次执行 build 和 sync 逻辑, 也就是进入 processUpdate 流程。processUpdate 接收到信息( hash, module) 之后, 进入 module.hot.check 和 module.hot.apply 流程。

客户端热更新

首先我们再来看看 module.hot 初始化实现逻辑

module.hot 初始化

webpack_require 函数定义时,通过 hotCreateModule 为每个 module 初始化 hot 逻辑

function __webpack_require__(moduleId){

var module = installedModules[moduleId] = {

i: moduleId,

l: false,

exports: {},

hot: hotCreateModule(moduleId), // 前端通过 ajax 获取热更新文件内容

parents:xxxx,

children: []

};

return module.exports;

}

hotCreateModule 实现

function hotCreateModule(moduleId){

var hot = {

accept: function(dep, callback){

},

check: hotCheck,

apply: hotApply,

status: function(l){},

.....

}

return hot;

}

hotCheck:前端通过 ajax 获取热更新文件内容

从 热更新一探:[进行多次热更新效果] 上面截图可以看到,每次服务端发送的消息(EventStrean) 的 hash 将作为下次 hot-update.json 和 hot-update.js 文件的 hash。也就是下面客户端更新当前

hotCurrentHash 值,作为下次的 hot-update.json 和 hot-update.js 更新请求。

function hotCheck(){

return new Promise(function(resolve, reject){

var __webpack_require__.p + "" + hotCurrentHash + ".hot-update.json";

var request = new XMLHttpRequest();

request.open("GET", requestPath, true);

request.timeout = requestTimeout;

request.send(null);

request.onreadystatechange = function(){

if(request.readyState === 4 && request.status === 200){

reject(new Error("Manifest request to " + requestPath + " failed."));

}else{

resolve(JSON.parse(request.responseText));

}

});

}).then(function(update){

// {h: "dcc99b114b8c64461a2e", c: {5: true}}

// 新的hotUpdateHash

hotUpdateNewHash = update.h;

// 向文档插入 hot-update.js script

hotEnsureUpdateChunk();

});

},

hotEnsureUpdateChunk 实现

hotEnsureUpdateChunk 函数的逻辑是向 HTML 文档插入 hot-update.js script 脚本。 hotEnsureUpdateChunk 调用 hotDownloadUpdateChunk 函数

function hotDownloadUpdateChunk(chunkId){

var head = document.getElementsByTagName("head")[0];

var script = document.createElement("script");

script.type = "text/javascript";

script.charset = "utf-8";

script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";

head.appendChild(script);

}

开启更新机制

开启热更新构建后, 每个 Vue 组件构建的代码都有下面这么一段 hotAPI 代码:

/* hot reload */

Component.options.__file = "app/web/page/about/about.vue"

if (true) {(function (){

var hotAPI = __webpack_require__(3)

hotAPI.install(__webpack_require__(0), false)

if (!hotAPI.compatible) return

module.hot.accept()

if (!module.hot.data) {

hotAPI.createRecord("data-v-aafed0d8", Component.options)

} else {

hotAPI.reload("data-v-aafed0d8", Component.options)

' + ' }

module.hot.dispose(function (data){

disposed = true

})

})()}

createRecord 和 reload 触发 UI 更新

获取 Vue 组件的 render,重新 render 组件, 继而实现 UI 无刷新更新。

function makeOptionsHot(id, options){

if (options.functional) {

// 获取组件的render 方法,重新 render

var render = options.render

options.render = function (h, ctx){

var instances = map[id].instances

if (instances.indexOf(ctx.parent) < 0) {

instances.push(ctx.parent)

}

return render(h, ctx)

}

} else {

injectHook(options, 'beforeCreate', function(){

var record = map[id]

if (!record.Ctor) {

record.Ctor = this.constructor

}

record.instances.push(this)

})

injectHook(options, 'beforeDestroy', function(){

var instances = map[id].instances

instances.splice(instances.indexOf(this), 1)

})

}

}

热更新流程总结

format,png

webpack-hot-update.png

Webpack编译期,为需要热更新的 entry 注入热更新代码(EventSource通信)

页面首次打开后,服务端与客户端通过 EventSource 建立通信渠道,把下一次的 hash 返回前端

客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash

修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端

客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档

hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。

关键代码


http://chatgpt.dhexx.cn/article/6lRdfnHk.shtml

相关文章

webpack4.0核心概念(十)—— HMR(热模块替换-局部刷新)

HMR&#xff1a;当修改一个js或者css的时候&#xff0c;只刷新修改的内容&#xff0c;不进行整个页面的刷新。 css的HMR——只支持开发环境 不能使用mini-css-extract-plugin需要使用style-loader,因为它不支持抽离出的css,需要用style-loader ① 在webpack.config.js中配置 …

前端工程化——Livereload和HMR、本地开发服务器

目录 本地开发服务器解决的问题 动态构建 Mock服务 动态构建 源码改动之后&#xff0c;浏览器应该在何时获取重新编译后的资源&#xff1f; Livereload和HMR 有了构建系统的支持&#xff0c;前端开发人员可以使用诸多有利于开发和维护的技术进行源代码编写。然而如果在开…

hmr webpack 不编译_webpack hmr

参考&#xff1a; hmr技术支持程序运行时的模块(amd、commonJS等)的修改、添加和删除&#xff0c;而不用整个程序重新加载&#xff0c;这可以提升开发的效率&#xff1a; hmr后程序的状态可以得到保存 仅仅改变变化的部分&#xff0c;其余不变 调样式更加快捷&#xff0c;基本比…

18.webpack4之HMR

1.HMR&#xff08;Hot Module Replacement&#xff09;热模块替换 在开发环境&#xff0c;可以使用热模块替换&#xff08;HMR&#xff09;去实现如果一个模块发生变化&#xff0c;只会重新打包这一个模块&#xff08;而不是所有模块都进行打包&#xff09;&#xff0c;而无需重…

webpack5之HMR原理探究

一、概念介绍 模块热替换(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新所有类型的模块&#xff0c;而无需完全刷新。 主要是通过以下几种方式&#xff0c;来显著加快开发速度&#xff1a; 保留在完全重新加载页面期间丢失的应用程…

hmr webpack 不编译_一文搞懂 webpack HMR 原理

关注「前端向后」微信公众号&#xff0c;你将收获一系列「用心原创」的高质量技术文章&#xff0c;主题包括但不限于前端、Node.js以及服务端技术 一.HMR Hot Module Replacement(HMR)特性最早由 webpack 提供&#xff0c;能够对运行时的 JavaScript 模块进行热更新(无需重刷&a…

Webpack HMR 原理全解析

执行 npx webpack serve 命令后&#xff0c;WDS 调用 HotModuleReplacementPlugin 插件向应用的主 Chunk 注入一系列 HMR Runtime&#xff0c;包括&#xff1a; 用于建立 WebSocket 连接&#xff0c;处理 hash 等消息的运行时代码 用于加载热更新资源的 RuntimeGlobals.hmrDow…

vite1.x 热更新(HMR)的实现原理

前言 将近一年前自己尝试阅读vite源码&#xff08;2.x&#xff09;&#xff0c;虽然也有些收获但整体并没有到达我的预期&#xff0c;对于vite也是停留在一知半解的程度上。最近想重新开始学习vite&#xff0c;但回顾之前的学习历程&#xff0c;感觉不太想继续之前的方式&…

Webpack HMR 原理解析

Hot Module Replacement&#xff08;以下简称 HMR&#xff09;是 webpack 发展至今引入的最令人兴奋的特性之一 &#xff0c;当你对代码进行修改并保存后&#xff0c;webpack 将对代码重新打包&#xff0c;并将新的模块发送到浏览器端&#xff0c;浏览器通过新的模块替换老的模…

Webpack的HMR原理解析

Hot Module Replacement&#xff08;以下简称 HMR&#xff09;是 webpack 发展至今引入的最令人兴奋的特性之一 &#xff0c;当你对代码进行修改并保存后&#xff0c;webpack 将对代码重新打包&#xff0c;并将新的模块发送到浏览器端&#xff0c;浏览器通过新的模块替换老的模…

Esbuild Bundler HMR

Esbuild 虽然 bundler 非常快&#xff0c;但是其没有提供 HMR 的能力&#xff0c;在开发过程中只能采用 live-reload 的方案&#xff0c;一有代码改动&#xff0c;页面就需要全量 reload &#xff0c;这极大降低开发体验。为此添加 HMR 功能至关重要。 经过调研&#xff0c;社…

Vite HMR

传统webpack的hmr是使用webpack的HotModuleReplacementPlugin&#xff0c;而vite则是采用native ES Module的devServer。 初始化本地服务器加载并运行对应的plugin 最重要的一件事就是运行plugin&#xff0c;目前vite支持的plugin大体如下图所示 1、建立ViteDevServer服务器…

webpack HMR

HMR或者hot模式下&#xff0c;启动webpack会在浏览器与服务器之间会建立一个websocket连接&#xff0c;使得浏览器可以和服务端建立全双工通信&#xff1b;当应用程序的代码更新时&#xff0c;会要求HMR runtime检查更新&#xff0c;有更新时&#xff0c;在websoket连接中会返回…

webpack中的HMR(热更新)原理剖析

简介 Hot Module Replacement&#xff08;以下简称 HMR&#xff09;是 webpack 发展至今引入的最令人兴奋的特性之一 &#xff0c;当你对代码进行修改并保存后&#xff0c;webpack 将对代码重新打包&#xff0c;并将新的模块发送到浏览器端&#xff0c;浏览器通过新的模块替换…

HMR(热替换)

HMR 即模块热替换&#xff08;hot module replacement&#xff09;的简称&#xff0c;它可以在应用运行的时候&#xff0c;不需要刷新页面&#xff0c;就可以直接替换、增删模块。webpack 可以通过配置 webpack.HotModuleReplacementPlugin 插件来开启全局的 HMR 能力&#xff…

面试官:说说Webpack的热更新是如何做到的?原理是什么?

一、是什么 HMR全称 Hot Module Replacement&#xff0c;可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用 例如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致整…

Webpack 热更新HMR 原理全解析

一、什么是 HMR HMR 全称 Hot Module Replacement&#xff0c;中文语境通常翻译为模块热更新&#xff0c;它能够在保持页面状态的情况下动态替换资源模块&#xff0c;提供丝滑顺畅的 Web 页面开发体验。 HMR 最初由 Webpack 设计实现&#xff0c;至今已几乎成为现代工程化工具…

curl.perform() pycurl.error: (23, 'Failed writing body (0 != 59)')

在使用python3.7编码时&#xff0c;引入pycurl模块和StringIO模块后&#xff0c;容易引起上述错误 导入StringIO模块的解决方案&#xff1a; 只有在python2中才能导入StringIO模块&#xff0c;直接 from StringIO import StringIO 即可 但是python3&#xff0c;STringIO和…

关于python的url处理

基本环境&#xff1a; python2.7 1 完整的url语法格式&#xff1a; 协议://用户名密码:子域名.域名.顶级域名:端口号/目录/文件名.文件后缀?参数值#标识 2 urlparse模块对url的处理方法 urlparse模块对url的主要处理方法有&#xff1a;urljoin/urlsplit/urlunsplit/urlpar…

windows10+python3.7使用pip安装pycurl失败

使用pip install pycurl安装pycurl失败&#xff1a; python setup.py egg_info did not run successfully. 可以单独下载pycurl依赖文件然后安装 sArchived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu) 选择Python对应版本的文件进行下载&#xff0…