当前位置: 首页 > 后端技术 > Node.js

WebpackHMR原理解析

时间:2023-04-03 19:24:35 Node.js

HotModuleReplacement(简称HMR)包括以下内容:热更新图热更新步骤讲解第一步:webpack监视文件系统并打包到内存中webpack-dev-middleware调用webpackapi来fileSystemwatch,当文件发生变化时,webpack重新编译打包文件,然后保存在内存中。webpack将bundle.js文件打包到内存中。之所以不生成文件,是因为访问内存中的代码比访问文件系统中的文件速度更快,同时也减少了将代码写入文件的开销。这一切都归功于memory-fs,memory-fs是webpack-dev-middleware的一个依赖库,webpack-dev-middleware将webpack原有的outputFileSystem替换为一个MemoryFileSystem实例,这样代码就会输出到内存中。webpack-dev-middleware这部分的源码如下://compiler//webpack-dev-middleware/lib/Shared.jsvarisMemoryFs=!compiler.compilers&&compiler.outputFileSysteminstanceofMemoryFileSystem;如果(isMemoryFs){fs=编译器。输出文件系统;}else{fs=compiler.outputFileSystem=newMemoryFileSystem();}第二步:devServer通知浏览器文件变化在启动devServer时,sockjs)在服务器和浏览器之间建立一个webSocket长连接,目的是为了通知浏览器webpack编译打包各个阶段的状态。最关键的一步是调用webpackapi监听compile的done事件。当编译完成后,webpack-dev-server会通过_sendStatus方法将编译好的新模块的hash值发送给浏览器。//webpack-dev-server/lib/Server.jscompiler.plugin('done',(stats)=>{//stats.hash是最新包文件的哈希值this._sendStats(this.sockets,stats.toJson(clientStats));this._stats=stats;});...Server.prototype._sendStats=function(sockets,stats,force){if(!force&&stats&&(!stats.errors||stats.errors.length===0)&&stats.assets&&stats.assets.every(asset=>!asset.emitted)){returnthis.sockWrite(sockets,'still-ok');}//调用sockWrite方法转换hash值通过websocket发送给浏览器this.sockWrite(sockets,'hash',stats.hash);如果(stats.errors.length>0){this.sockWrite(sockets,'errors',stats.errors);}elseif(stats.warnings.length>0){this.sockWrite(sockets,'warnings',stats.warnings);}else{this.sockWrite(sockets,'ok');}};第三步:webpack-dev-server/client响应收到的服务器消息ndle.js文件将接收websocket消息的代码。webpack-dev-server/client收到typehash消息后会暂存hash值,收到typeok消息后对应用执行reload操作。在reload操作中,webpack-dev-server/client会根据热配置决定是刷新浏览器还是对代码进行热更新(HMR)。代码如下://webpack-dev-server/client/index.jshash:functionmsgHash(hash){currentHash=hash;},好的:functionmsgOk(){//...reloadApp();},//...functionreloadApp(){//...if(hot){log.info('[WDS]App热更新...');consthotEmitter=require('webpack/hot/emitter');hotEmitter.emit('webpackHotUpdate',currentHash);//...}else{log.info('[WDS]应用已更新。正在重新加载...');self.location.reload();}}第四步:Webpack收到最新的哈希值校验,请求模块代码。首先webpack/hot/dev-server(以下简称dev-server)监听第三步webpack-dev-server/client发送的webpackHotUpdate消息,调用webpack/lib/HotModuleReplacement.runtime(HMR运行时为short)check方法来检测是否有新的更新。检查过程中会用到webpack/lib/JsonpMainTemplate.runtime(简称jsonpruntime)中的hotDownloadManifest和hotDownloadUpdateChunk两个方法。hotDownloadManifest调用AJAX向服务器请求是否有更新的文件,如果有,它会将更新的文件列表发送回浏览器。此方法返回最新的哈希值。hotDownloadUpdateChunk通过jsonp请求最新的模块代码,然后将代码返回给HMRruntime,HMRruntime会根据返回的新模块代码做进一步的处理,可能是刷新页面,也可能是热更新模块。该方法返回最新哈希值对应的代码块。最后将新的代码块返回给HMR运行时进行模块热更新。附:为什么第三步更新模块的代码不是直接通过websocket发送给浏览器,而是通过jsonp获取?我的理解是,对于功能块的解耦,各模块各司其职。dev-server/client只负责消息的传递,不负责新模块的获取。这些任务应该由HMRruntime来完成,而HMRruntime应该是获取新代码的地方。此外,因为在不使用webpack-dev-server的前提下,使用webpack-hot-middleware和webpack同样可以完成模块热更新过程。使用webpack-hot-middleware有一件有趣的事情。它不使用websocket,而是使用的EventSource。综上所述,在HMR的工作流程中,新的模块代码不应该放在websocket消息中。第五步:HotModuleReplacement.runtime对模块进行热更新。这一步是整个模块热更新(HMR)的关键步骤,模块热更新发生在HMR运行时的hotApply方法中//webpack/lib/HotModuleReplacement.runtimefunctionhotApply(){//...varidx;var队列=过时的模块。片();while(queue.length>0){moduleId=queue.流行音乐();module=installedModules[moduleId];//...//从缓存中删除模块deleteinstalledModules[moduleId];//处理时无需调用处理程序deleteoutdatedDependencies[moduleId];//移除所有孩子的“父母”引用for(j=0;j=0){child.parents.splice(idx,1);}}}//...//为(appliedUpdate中的moduleId)插入新代码{if(Object.prototype.hasOwnProperty.call(appliedUpdate,moduleId)){}}//...}模块热更新的错误处理,如果热更新过程中出现错误,热更新会回退到刷新浏览器。这部分代码在dev-server代码中。简要代码如下:module.hot.check(true).then(function(updatedModules){if(!updatedModules){returnwindow.location.reload();}//...}).catch(function(错误){varstatus=module.hot.status();if(["abort","fail"].indexOf(status)>=0){window.location.reload();}});第六步:业务代码需要做什么?将旧模块替换为新模块代码后,我们的业务代码无法知道代码发生了变化,即修改hello.js文件时,需要调用index.js文件中HMR的accept方法,添加模块更新后的处理函数,及时将hello方法的返回值插入到页面中。代码如下//index.jsif(module.hot){module.hot.accept('./hello.js',function(){div.innerHTML=hello()})}更多内容在我的Githubhttps://github.com/zhongmeizh...参考:饿了么前端