webpack热更新原理 http和https的默认端口号( 二 )



webpack热更新原理 http和https的默认端口号

文章插图
那么读者最大的困惑可能变成了:精度怎么粗糙的热更新 。好像跟直接刷页面并没有什么区别?
如果我们要进行精度更高的热更新 。那么带来的性能差异其实是巨大的 。我们来考虑一下如果我们希望尽可能细粒度的热更新操作 。接下来需要哪些操作:
读取文件
构造语法树
对比和之前的语法树的差异
通信将差异传给客户端
将差异转换为对应的 DOM 操作
那样不可避免的 。我们就要在内存中缓存每个页面最初的语法树 。对于模块化的组件来说 。HTML 本身的变更其实是并不太多的 。没有必要进行这么复杂的操作
CSS
CSS 也比较简单 。只要移除旧的 CSS 文件重新引入就能更新 CSS 了 。这次 。我们的代码将会更加精简 。
//监听if(path.endsWith('.css')){constmessage=JSON.stringify({type:'css',content:path.split('static/')[1]})ws.send(message)}//注入if(data.type==='css'){consthost=location.hostdocument.querySelectorAll('link[rel="stylesheet"]').forEach(el=>{constresource=el.href.split(host+'/')[1]console.log(resource)if(resource===data.content)el.remove()})document.head.insertAdjacentHTML('beforeend','<linkrel="stylesheet"href=https://www.wangchuang8.com/"'+data.content+'"/>')console.log('[HMR]updatedCSS');}
webpack热更新原理 http和https的默认端口号

文章插图

webpack热更新原理 http和https的默认端口号

文章插图
相比 HTML 来说 。CSS 显得更加「无公害」——即使是整个文件替换更新 。也不会带来什么坏处 。甚至你都不需要对文件内容进行读取 。只需要重新加载文件内容 。
javaScript
最大的难点在于 JavaScript 热更新的实现 。如果我们参考 HTML 和 CSS 的实现 。简单的进行二次写入 。很快的就会遇到各种各样的问题 。在这里 。我们通过 eval 的方式进行再写入 。
假设我们对按钮绑定了一个点击事件 。console.log(123) 。然后变成 console.log(1) 。使用原本的方法写入之后 。就会响应两次事件 。分别输出 「123」和「1」 。(这里就不贴代码了 。感兴趣的同学可以自己做这个实验)
但是如同 HTML 的实现部分一样 。我们并不像进行复杂的语法树构建来感知操作的是哪一个 DOM 。那么这个需求就变的很难处理 。
得益于组件化 。我们现在并不用太过关心这个问题 。当我更新了一个文件的时候 。我必然是更新了一个组件 。只需要把这个组件的实例化移除并且重新载入即可 。那样与之绑定的相关事件也会被删除 。
整理一下思路 。要执行 JS 的热更新 。我们大概会有以下几个步骤:
感知每一个热更新的组件:建立一个 k-v 结构 。确保存入每个组件的实例 。便于之后更新时删除 DOM 并且更新
执行 eval 写入代码
遍历 k-v 结构 。删除原先创建的 DOM 。而实例渲染到 DOM 中的步骤是由框架本身处理的 。我们甚至可以不用做任何操作
这里我们以我最近在使用的那个无需构建即可运行的前端框架为例 。从上述步骤中 。我们可以知道 。最重要的就是要劫持构造函数 。在转换为 DOM 时存入我们的 k-v 结构 。方便以后使用 。
//劫持构造函数constJKL=window.Jinkelaconststorage={}letlatest=truewindow.Jinkela=classjklextendsJKL{constructor(...args){super(...args)constvalues=storage[this.constructor.name]if(!latest){storage[this.constructor.name].forEach(el=>el.remove())storage[this.constructor.name]=[]latest=true}storage[this.constructor.name]=values?[...values,this.element]:[this.element]}}//注入if(data.type==='js'){latest=falseeval(data.content)console.log('[HMR]updatedJS');}
webpack热更新原理 http和https的默认端口号

文章插图

webpack热更新原理 http和https的默认端口号

文章插图
这样在执行 eval 的过程中就会先记性一遍 DOM 的整理 。执行完毕后新的组件就被渲染上去了 。
当然 。读者可以发现这里有一个前提条件 。那就是没有一个内容处于全局作用域 。否则就会遇到重复声明的 error 导致热更新失败 。
基本上来说是一个非常简单的 Hot Reload 。可以完善的地方还是相当多的:
没有维持连接的心跳包
频繁对磁盘文件读
降级 Live Reload 的操作
目前这种 Hot Reload 只支持单文件组件
不支持继承
【webpack热更新原理 http和https的默认端口号】

推荐阅读