第 70 题: 介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面
Hot Module Replacement(以下简称 HMR)是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下就能够对应用进行更新。例如,在开发 Web 页面过程中,当你点击按钮,出现一个弹窗的时候,发现弹窗标题没有对齐,这时候你修改 CSS 样式,然后保存,在浏览器没有刷新的前提下,标题样式发生了改变。感觉就像在 Chrome 的开发者工具中直接修改元素样式一样。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
上面有几个评论只是在介绍步骤,能再深入点吗?感觉就像是在介绍代码调用了哪些函数,都没有深入解析这些函数内部的实现原理。
我觉得这问题可以换一种问法:有 A、B 两个 js 模块,现要将当前使用的 A 模块替换成 B 模块,如何用最简单的代码实现热更新?
webpack
打包webpack
将新模块通过websocket
推送到浏览器我现在在自己写一个比较简陋的demo。发现文件变动,建立websocket,浏览器更新对应模块都好说。但是最大的问题是更新了模块之后呢?如果只是重新__require__该模块没办法刷新页面,引入之后只是返回了一个对象,里面的函数不知道运行哪一个。不知道是不是我流程搞错了,希望有大佬能解答一下
掘金政采云有篇文章写的很好,整体流程大概如下:
官方解答
所以知道了之后,能做什么呢?装逼吗?记住几个步骤,然后背出来?为了知道而知道?还是说你遇到了问题才去研究,自己去看?
在 webpack 中,都是模块且有唯一标识。在 webpack 编译完成后,将修改的模块 hash 对应的模块重新执行。就达到了局部刷新的效果。
过程
hotDownloadManifest
返回执行
installedModules
所有的子模块上面说的都太复杂了,一句话:
有个前提是模块的名称不会变化,所以在开发期间的webpack配置的chunkId不能是hash,因为模块名称如果变化,webpack脚手架就找不到新的文件在浏览器中模块的位置,自然无法局部更新这个模块。
这个问题也许可以通过新旧文件名的map映射解决,但是同样的依赖这个模块的模块内容也变更了,因为引用链接变了,由此蔓延,就是说牵一发动全身,所有的模块内容都可能要更新,HMR也就没意义了。
但是在部署生产环境的的时候,为了防止浏览器缓存模块,一般是要做hash处理的。如果使用现成的什么create-react/vue之类的脚手架就不用关心这个,因为别人已经帮你处理好了,如果自己手动搭建要注意。
关于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() 完成热更新;
欢迎拍砖!
还记得 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
看得有点蒙
太长了 能用10句话内总结下吗
为什么HMR用webSocket通知需要更新时不直接把更新后的模块信息发过去呢?还要多一步HMR runtime主动去请求
当时略微看了一下HMR的代码Webpack4和实现,主要要点在于:
可以参见一个不太成熟的源码解析:
https://juejin.im/post/5c86ec276fb9a04a10301f5b
简单来说就是: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 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。
你们这些人我服了,这原连接地址,我真的不知道说什么好
给大家讲个背景,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
1.当修改了一个或多个文件;
2.文件系统接收更改并通知webpack;
3.webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
4.HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
5.HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。