第 70 题: 介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面

发布于 2022-08-01 23:31:07 字数 264 浏览 209 评论 19

Hot Module Replacement(以下简称 HMR)是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下就能够对应用进行更新。例如,在开发 Web 页面过程中,当你点击按钮,出现一个弹窗的时候,发现弹窗标题没有对齐,这时候你修改 CSS 样式,然后保存,在浏览器没有刷新的前提下,标题样式发生了改变。感觉就像在 Chrome 的开发者工具中直接修改元素样式一样。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

空城之時有危險 2022-05-04 13:56:23

上面有几个评论只是在介绍步骤,能再深入点吗?感觉就像是在介绍代码调用了哪些函数,都没有深入解析这些函数内部的实现原理。

我觉得这问题可以换一种问法:有 A、B 两个 js 模块,现要将当前使用的 A 模块替换成 B 模块,如何用最简单的代码实现热更新?

冰魂雪魄 2022-05-04 13:56:23
  1. 修改代码,触发webpack打包
  2. webpack将新模块通过websocket推送到浏览器
  3. 浏览器使用新模块替换旧模块
潜移默化 2022-05-04 13:56:23

我现在在自己写一个比较简陋的demo。发现文件变动,建立websocket,浏览器更新对应模块都好说。但是最大的问题是更新了模块之后呢?如果只是重新__require__该模块没办法刷新页面,引入之后只是返回了一个对象,里面的函数不知道运行哪一个。不知道是不是我流程搞错了,希望有大佬能解答一下

金兰素衣 2022-05-04 13:56:23

掘金政采云有篇文章写的很好,整体流程大概如下:

WDS 启动本地服务 (new webpack --> 启动 server --> 启动 websocket{将 websocket 的代码注入到浏览器代码中}
--> webpack 开始监听文件变动{变动了就重新编译构建} --> HMR-Plugin 将热更新代码注入到 浏览器运行代码中,也就是 HRM runtime)
--> HRM runtime 删除过期的模块,替换为新的模块,然后开始执行相关代码
寂寞陪衬 2022-05-04 13:56:23
网上的这些关于HMR的文章都是为了博眼球吸流量的,放下浮躁的心好好看看源码
webpack.HotModuleReplacementPlugin负责比对module的hash生成
  核心代码
  hotUpdateMainContentByFilename.set(filename, {							      
      removedChunkIds,
      removedModules,
      updatedChunkIds,
      assetInfo
    });

webpack-hot-middleware负责获取HotModuleReplacementPlugin对比出来的chunk信息丢给客户端
  服务端核心代码
    function onDone(statsResult) {
      if (closed) return;
      // Keep hold of latest stats so they can be propagated to new clients
      latestStats = statsResult; // 取到最新的chunk的stats信息
      publishStats('built', latestStats, eventStream, opts.log);
    }
    compiler.hooks.done.tap('webpack-hot-middleware', onDone); // webpack的插件钩子,一次更新编译后完成调用
    eventStream.publish({ // 通过EventSource长链接推送给前端,(这里还有很多逗比说HMR是websocket,sock.js实现的)
      name: name,
      action: action,
      time: stats.time, // stats 就是 onDone 取到的chunk信息
      hash: stats.hash,
      warnings: stats.warnings || [],
      errors: stats.errors || [],
      modules: buildModuleMap(stats.modules),
    });
  客户端核心代码
    var applyResult = module.hot.apply(applyOptions, applyCallback); // 接收到变更的chunk信息

在webpack包下的hrm目录下有个LazyCompilationPlugin.js文件
  核心代码
  Template.indent([
    "module.hot.accept();",
    `module.hot.accept(${JSON.stringify(
      chunkGraph.getModuleId(module)
    )}, function() { module.hot.invalidate(); });`,
    "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });",
    "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);" // 得到最新的chunk代码
  ]),

总结起来就是:webpack.HotModuleReplacementPlugin负责生成变更的chunk信息 -> webpack-hot-middleware负责接受到这些信息然后通过EventSource丢给客户端 -> 同时webpack-hot-middleware也负责接受chunk信息传递给module.hot链 -> webpack通过hot.accept传入文件名获取对应的moduleId然后在hot.data中取代码

以上是花费30分钟看webpack源码的结果,以后有时间有机会写文章详述
烟柳画桥 2022-05-04 13:56:22

所以知道了之后,能做什么呢?装逼吗?记住几个步骤,然后背出来?为了知道而知道?还是说你遇到了问题才去研究,自己去看?

谈情不如逗狗 2022-05-04 13:56:14

在 webpack 中,都是模块且有唯一标识。在 webpack 编译完成后,将修改的模块 hash 对应的模块重新执行。就达到了局部刷新的效果。

过程

  • webpack-dev-middleware 是用来处理文件打包到哪里,到内存读取速度更快。
  • devServer 在监听 compiler done 后,利用 socket 告诉 devServer/client 修改模块的 hash
  • HMR.runtime 利用 HTTP 请求 hash.hot-update.json 获取更新模块列表 hotDownloadManifest
    {"h":"11ba55af05df7c2d3d13","c":{"index-wrap":true}}
  • 再通过 HTTP (jsonp) 获取更新模块的 js
    index-wrap.7466b9e256c084c8463f.hot-update.js

    返回执行

    webpackHotUpdate("index-wrap", {
      // ....
    })
  • webpackHotUpdate 做了三件事
    • 找到过期的模块和依赖并从缓存中删除
      delete installedModules[moduleId];
      delete outdatedDependencies[moduleId];
    • 遍历所有的 module.children,重新 installedModules 所有的子模块
    • 最后将自身模块的内容做替换修改
      modules[moduleId] = appliedUpdate[moduleId]
  • 最后代码替换之后并没有重新执行,需要手动注册需要重新执行的模块方法
      if (module.hot) {
         module.hot.accept('./print.js', function() {
           console.log('Accepting the updated printMe module!');
           printMe();
         })
       }
誰認得朕 2022-05-04 13:56:14

上面说的都太复杂了,一句话:

每个模块都有个名称,当文件内容改变时,通过建立好的socket通知浏览器;
然后页面端的webpack脚手架代码会重载这个模块文件。

有个前提是模块的名称不会变化,所以在开发期间的webpack配置的chunkId不能是hash,因为模块名称如果变化,webpack脚手架就找不到新的文件在浏览器中模块的位置,自然无法局部更新这个模块。
这个问题也许可以通过新旧文件名的map映射解决,但是同样的依赖这个模块的模块内容也变更了,因为引用链接变了,由此蔓延,就是说牵一发动全身,所有的模块内容都可能要更新,HMR也就没意义了。

但是在部署生产环境的的时候,为了防止浏览器缓存模块,一般是要做hash处理的。如果使用现成的什么create-react/vue之类的脚手架就不用关心这个,因为别人已经帮你处理好了,如果自己手动搭建要注意。

梦在深巷 2022-05-04 13:55:42

关于webpack的热更新原理,面试官比较想听到的是工作流程和关键点,非“流水账”式的源码分析。我认为可以这样的介绍:

首先,介绍webpack-dev-server:
webpack-dev-server 主要包含了三个部分:
1.webpack: 负责编译代码
2.webpack-dev-middleware: 主要负责构建内存文件系统,把webpack的 OutputFileSystem 替换成 InMemoryFileSystem。同时作为Express的中间件拦截请求,从内存文件系统中把结果拿出来。
3.express:负责搭建请求路由服务。

其次,介绍工作流程:
1.启动dev-server,webpack开始构建,在编译期间会向 entry 文件注入热更新代码;
2.Client 首次打开后,Server 和 Client 基于Socket建立通讯渠道;
3.修改文件,Server 端监听文件发送变动,webpack开始编译,直到编译完成会触发"Done"事件;
4.Server通过socket 发送消息告知 Client;
5.Client根据Server的消息(hash值和state状态),通过ajax请求获取 Server 的manifest描述文件;
6.Client对比当前 modules tree ,再次发请求到 Server 端获取新的JS模块;
7.Client获取到新的JS模块后,会更新 modules tree并替换掉现有的模块;
8.最后调用 module.hot.accept() 完成热更新;

欢迎拍砖!

打小就很酷 2022-05-04 13:52:04

1.当修改了一个或多个文件;
2.文件系统接收更改并通知webpack;
3.webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
4.HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
5.HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。

为什么HMR用webSocket通知需要更新时不直接把更新后的模块信息发过去呢?还要多一步HMR runtime主动去请求

还记得 HMR 的工作原理图解 中的问题 3 吗?为什么更新模块的代码不直接在第三步通过 websocket 发送到浏览器端,而是通过 jsonp 来获取呢?我的理解是,功能块的解耦,各个模块各司其职,dev-server/client 只负责消息的传递而不负责新模块的获取,而这些工作应该有 HMR runtime 来完成,HMR runtime 才应该是获取新代码的地方。再就是因为不使用 webpack-dev-server 的前提,使用 webpack-hot-middleware 和 webpack 配合也可以完成模块热更新流程,在使用 webpack-hot-middleware 中有件有意思的事,它没有使用 websocket,而是使用的 EventSource。综上所述,HMR 的工作流中,不应该把新模块代码放在 websocket 消息中。

来源自:
https://zhuanlan.zhihu.com/p/30669007

你与昨日 2022-05-04 13:50:05

看得有点蒙

铁憨憨 2022-05-04 13:32:36

参考链接

太长了 能用10句话内总结下吗

ぽ尐不点ル 2022-05-04 13:27:39

1.当修改了一个或多个文件;
2.文件系统接收更改并通知webpack;
3.webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
4.HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
5.HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。

为什么HMR用webSocket通知需要更新时不直接把更新后的模块信息发过去呢?还要多一步HMR runtime主动去请求

骄傲 2022-05-04 12:08:44

当时略微看了一下HMR的代码Webpack4和实现,主要要点在于:

  1. 在页面使用socket和http-server建立链接
  2. module.hot.accpet来添加HMR的js模块
  3. 当对应模块发生变化,发送socket请求和client的chunk头进行比较
  4. 如果有变化,头部追加script脚本chunk

可以参见一个不太成熟的源码解析:
https://juejin.im/post/5c86ec276fb9a04a10301f5b

通知家属抬走 2022-05-03 21:53:52

简单来说就是:hot-module-replacement-plugin 包给 webpack-dev-server 提供了热更新的能力,它们两者是结合使用的,单独写两个包也是出于功能的解耦来考虑的。
1)webpack-dev-server(WDS)的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。
2)hot-module-replacement-plugin 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。

过度放纵 2022-05-03 20:46:35

你们这些人我服了,这原连接地址,我真的不知道说什么好

明媚殇 2022-05-03 15:41:43

给大家讲个背景,sean larkin(webpack core developer)提过一个事:
2014年时,redux作者Dan Abramov 在 stackoverflow中提过一个问题: What exactly the webpack HMR?
然后webpack founder Tobias K. 在问题下面详细回答了HMR的原理。
https://stackoverflow.com/questions/24581873/what-exactly-is-hot-module-replacement-in-webpack

执妄 2022-05-03 00:32:39

1.当修改了一个或多个文件;
2.文件系统接收更改并通知webpack;
3.webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
4.HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
5.HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。

~没有更多了~

关于作者

咿呀咿呀哟

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

胡图图

文章 0 评论 0

zt006

文章 0 评论 0

z祗昰~

文章 0 评论 0

冰葑

文章 0 评论 0

野の

文章 0 评论 0

天空

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文