当前位置: 首页 > Web前端 > JavaScript

HMR系列一:API

时间:2023-03-27 14:38:40 JavaScript

What(什么是HMR?)我们下面讨论的HMR是基于vite自己实现的一套HMR系统。vite实现的HMR是按照ESMHMR规范实现的。HMR:HotModuleReload模块热更新。之前我们在编辑器中更新代码的时候,会触发浏览器的页面刷新,但是这次刷新是全量刷新,相当于CMD+R。这时候页面的状态会被重置,总之体验不好。模块热更新就是为了解决这样的问题,只刷新我们编辑的代码对应的模块,保持页面的状态。可以看到我们这里编辑代码的时候,下面count的状态被保存了。刚刚热更新了上面文本部分的模块。Why(WhydoyouneedHMR?)其实每一个技术的诞生都是为了解决之前已经强调过的问题。HMR也是如此,其实原因上面已经说了。总结如下:为什么需要HMR?解决修改代码后页面全量更新问题,体验差的问题解决全量更新导致状态丢失的问题How(HMR怎么用?)vite中实现的HMR系统其实就是一层封装ESMHMR规范中的API。vite会主动监听文件的变化,然后触发相应的API,实现模块的热更新。那么首先我们简单的看一下APIAPIhmr这套规范中的API都是注入到import.meta的hot中的。我们访问的时候只需要import.meta.hot.[name]import.meta是浏览器内置的对象。【MDN】interfaceImportMeta{readonlyhot?:{readonlydata:any//======触发更新=====accept():void//accept(cb:(mod:any)=>void):voidaccept(dep:string,cb:(mod:any)=>void):voidaccept(deps:string[],cb:(mods:any[])=>void):void//==================prune(cb:()=>void):voiddispose(cb:(data:any)=>void):voiddecline():voidinvalidate():void//=====监听hmrevent====on(event:string,cb:(...args:any[])=>void):void//===================}}accept(cb)accept翻译为接受。在hmr中,他也是这个意思:接受这个热更新,接受这个热更新的模块称为HMR边界。当我们在文件中加入这行代码,就是手动开启文件模块的热更新。当这个文件中的代码更新时,它会收到本次热更新的结果。if(import.meta.hot){import.meta.hot.accept((mod)=>{console.log(mod,'==')})}:::dangeraccept中的mod是更新后的模块exported内容。:::比如我们的文件如下,exportingrenderandother:exportconstrender=()=>{//...}exportconstother=()=>{//...}if(import.meta.hot){import.meta.hot.accept((mod)=>{console.log(mod,'==')})}那么当我们更新这个文件里面的代码,在这个地方接受热更新time在mod中是:如果我们需要接受其中一个导出模块的更新,那么直接调用mod.render()或mod.other()更新页面最新的内容。如果你文件中的导出方式默认是exportdefaultxxx,那么mod就是mod.default上面代码中我们通过一个回调函数accept来主动触发热更新模块中的函数。因为我们的文件只声明了render等函数,并没有执行,所以需要在accpet回调中手动触发。事实上,在某些情况下,不需要传递回调函数。accept将执行当前更改文件中的最新内容。比如我们的文件是可执行文件(类似于自执行函数)。当我们导入这个文件时,会执行文件中的代码,比如下面这种情况://render.tsconstrender=()=>{constapp=document.querySelector('#app')!app.innerHTML=`

HelloVite12

Yes

`}render()if(import.meta.hot){import.meta.hot.accept()}//main.tsimport'./render.tx''执行render文件中的render函数,然后accept会重新执行文件,这当然会触发render函数。这个时候我们就不需要把回调函数传给accpet了。accept(dep,cb)accept方法还可以接收一个dep参数,该参数为当前页面热更新时所依赖的子模块的路径。此dep参数可以是单个字符串或字符串数??组。为数组时,表示依赖多个子模块hot){import.meta.hot.accept('./render.ts',(mod)=>{console.log(mod,'==')mod?.render()})}主模块依赖于渲染文件。当渲染文件发生变化时,它会收到热更新,因为此时它不依赖状态文件,所以当状态文件发生变化时,它会**重新加载页面**,而不是热更新。因为此时热更新的边界只有render模块,只有render模块发生变化才会触发主热更新//main.tsimport{render}from'./render'import{initsate}from'./state'render()initsate()if(import.meta.hot){import.meta.hot.accept(['./render.ts','./state.ts'],([mod1,mod2])=>{console.log(mod1,mod2,'==')mod1?.render()mod2?.initsate()})}此时state模块中的文件发生变化时,main的热更新就会也被触发。此时回调函数中的mod为:(因为只改变了state模块,mod1是undefined,也就是说render模块没有更新,符合预期。dispose()函数是比较简单,就是新模块更新之前的旧模块,模块销毁时的hook,用于清理旧模块中的一些副作用。consttimerId=setInterval(()=>{countEle.innerText=Number(countEle.innerText)+1+''},1000)if(import.meta.hot){import.meta.hot.dispose((data)=>{//清理副作用clearInterval(timerId)})}如果我们需要的模块中有timer之类的操作hmr,热更新后如果不提前销毁timer,会重复计时,所以可能会发生意想不到的效果。on(event,cb)监听自定义HMR事件。自定义HMR事件在服务器端定义和发送。在vite中,我们可以在插件中做到这一点。handleHotUpdate在vite插件中提供//vite-plugin.tx//其他代码省略handleHotUpdate({server}){server.ws.send({type:'custom',event:'xxx-file-change',//自定义事件名称data:{}//携带信息})return[]}//clientimport.meta.hot.on('xxx-file-change',(payload)=>{console.log(payload)})https://github.com/sanyuan0704/island.js/pull/79有时自定义hmr事件不会触发页面更新。我们可以通过监听自定义事件主动触发页面的重新渲染数据。该属性用于在同一个模块中共享更新前后的数据。这里绑定的数据不会被hmr影响或重置。import.meta.hot.data.count=1decline()表示此模块不可热更新,如果浏览器在传播HMR更新时遇到此模块,则应执行完全重新加载。invalidate()重新加载页面。下本书hmr的具体执行过程可以参考系列第二篇