当前位置:首页 > 引流 > 正文内容

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

admin3年前 (2022-10-10)引流996

我们大致了解了webpack HMR 原理。可以看出以下几点核心思想:

1、监听文件变化

2、服务器与客户端通信

3、替换流程

4、降级操作

当然,由于 webpack 本身有个很成熟的模块思想和生态,因此整个架构设计会比我们实现的 HMR 复杂很多。在模块热替换中,是由 webpack 的全部流程出力来完成这一操作的,而并没有局限于 webpack-dev-server 和 webpack 以及业务代码本身,实际上,起到更重要作用的是各类 loader,它们需要使用 HMR API 来实现 Hot Reload 的逻辑,决定什么时候注册模块、什么时候卸载模块;如何注册和卸载模块。而 webpack 本身更像是一个调用方的角色,不需要考虑具体的注册和反注册逻辑。

HMR 的核心组织

经过了上面的分析,我们基本上确认了一个思路,也就是分析 webpack HMR 得出的结论。但是由于我们只有 runtime,所以实现 Hot Reload 变成了一个下图的简单流程:

1、Server 启动一个 HTTP 服务器,并且注册和启动 WebSocket 服务,用于届时与客户端通信

2、在启动 Static 服务器后返回页面前注入 HMR 的客户端代码,业务方无需关心 HMR 的具体实现和添加对应的支持代码服务端监听磁盘文件的变更,将文件变更通过 WebSocket 发送给客户端

3、客户端收到文件变更消息后进行对应的模块处理

4、(模块处理失败,降级为 Live Reload)

live reload?

在实现 HMR 之前,我们可以先实现一个简单的 Live Reload 来保证我们 1-3 步的实现没有异常。

const Koa = require('koa')
const WebSocket = require('ws')
const chokidar = require('chokidar')
const app = new Koa()
const fs = require('fs').promises
const wss = new WebSocket.Server({ port: 8000 })
const dir = './static'
const watcher = chokidar.watch('./static', {
 ignored: /node_modules|.git|[/\]./
})
wss.on('connection', (ws) => {
 watcher
 .on('add', path => console.log(`File ${path} added`))
 .on('change', path => console.log(`File ${path} has been changed`))
 .on('unlink', path => console.log(`File ${path} has been moved`))
 .on('all', async (event, path) => {
 // Simple Live Reload
 ws.send('reload')
 })
 ws.on('message', (message) => {
 console.log('received: %s', message)
 })
 ws.send('HMR Client is Ready')
})
const injectedData = `<script>{
 const socket = new WebSocket('ws://localhost:8000');
 socket.addEventListener('open', (event) => {
 socket.send('[HMR] is Ready')
 console.log('[HMR] Start')
 });
 socket.addEventListener('message', function (event) {
 // Simple Live Reload
 if (event.data === 'reload') window.location.reload()
 })};
</script>`
app.use(async (ctx, next) => {
 let file = ctx.path
 if (ctx.path.endsWith('/')) {
 file = ctx.path + 'index.html'
 }
 let body
 try {
 body = await fs.readFile(dir + file, {
 encoding: 'utf-8'
 })
 } catch(e) {
 ctx.status = 404
 return next()
 }
 if (file.endsWith('.html')) body = body.replace('<body>', `<body>${injectedData}`)
 if (file.endsWith('.css')) ctx.type = 'text/css'
 ctx.body = body
 next()
})
app.listen(3001)
console.log('listen on port 3001')

手机看代码不方便,我把代码截图贴这里了

   

上述代码中,简单的使用了 chokidar 这个文件监听库,它极大的减轻了我们的工作量;而 WebSocket 和服务器的实现上暂不赘述,之所以不直接使用 koa-static 的原因是因为我们需要对于 HTML 文件进行一些注入操作,以上 Live Reload 的实现非常简单,基本可以总结为一句话:得知文件变化后向客户端发送 reload 消息,客户端收到消息执行页面刷新操作。

实现了一个 Live Reload 之后,接下来我们只需要变更注入的代码发送到客户端的消息两个部分即可,其实 Hot Reload 和 Live Reload 最大的区别也就是「最小模块替换」与「刷新页面」的区别,因此其他部分都是不用变动的。

替换 HTML 和 CSS 则是其中最简单的两项任务。

HTML

通常来说,我们要覆盖 HTML 中的内容,除了刷新这一操作外,还有一个就是 document.write(),实际上我们也是通过这个函数来实现 HTML 的 Hot Reload 的:

// 监听
 .on('all', async (event, path) => {
 if (path.endsWith('.html')) {
 body = await fs.readFile(path, {
 encoding: 'utf-8'
 })
 const message = jsON.stringify({ type: 'html', content: body })
 ws.send(message)
 }
 })
// 注入
let data = {}
try {
 data = JSON.parse(event.data)
} catch (e) {
 // return
}
console.log(data)
if (data.type === 'html') {
 document.write(data.content);
 document.close();
 console.log('[HMR] updated HTML');
}

     

那么读者最大的困惑可能变成了:精度怎么粗糙的热更新,好像跟直接刷页面并没有什么区别?

如果我们要进行精度更高的热更新,那么带来的性能差异其实是巨大的,我们来考虑一下如果我们希望尽可能细粒度的热更新操作,接下来需要哪些操作:

  1. 读取文件

  2. 构造语法树

  3. 对比和之前的语法树的差异

  4. 通信将差异传给客户端

  5. 将差异转换为对应的 DOM 操作

那样不可避免的,我们就要在内存中缓存每个页面最初的语法树,对于模块化的组件来说,HTML 本身的变更其实是并不太多的,没有必要进行这么复杂的操作

CSS

CSS 也比较简单,只要移除旧的 CSS 文件重新引入就能更新 CSS 了,这次,我们的代码将会更加精简。

// 监听
if (path.endsWith('.css')) {
 const message = JSON.stringify({ type: 'css', content: path.split('static/')[1] })
 ws.send(message)
}
// 注入
if (data.type === 'css') {
 const host = location.host
 document.querySelectorAll('link[rel="stylesheet"]').forEach(el => {
 const resource = el.href.split(host + '/')[1]
 console.log(resource)
 if (resource === data.content) el.remove()
 })
 document.head.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="' + data.content + '" />')
 console.log('[HMR] updated CSS');
}

     

相比 HTML 来说,CSS 显得更加「无公害」——即使是整个文件替换更新,也不会带来什么坏处,甚至你都不需要对文件内容进行读取,只需要重新加载文件内容。

javaScript

最大的难点在于 JavaScript 热更新的实现,如果我们参考 HTML 和 CSS 的实现,简单的进行二次写入,很快的就会遇到各种各样的问题。在这里,我们通过 eval 的方式进行再写入。

假设我们对按钮绑定了一个点击事件,console.log(123),然后变成 console.log(1),使用原本的方法写入之后,就会响应两次事件,分别输出 「123」和「1」。(这里就不贴代码了,感兴趣的同学可以自己做这个实验)

但是如同 HTML 的实现部分一样,我们并不像进行复杂的语法树构建来感知操作的是哪一个 DOM,那么这个需求就变的很难处理。

得益于组件化,我们现在并不用太过关心这个问题,当我更新了一个文件的时候,我必然是更新了一个组件,只需要把这个组件的实例化移除并且重新载入即可,那样与之绑定的相关事件也会被删除。

整理一下思路,要执行 JS 的热更新,我们大概会有以下几个步骤:

  1. 感知每一个热更新的组件:建立一个 k-v 结构,确保存入每个组件的实例,便于之后更新时删除 DOM 并且更新

  2. 执行 eval 写入代码

  3. 遍历 k-v 结构,删除原先创建的 DOM,而实例渲染到 DOM 中的步骤是由框架本身处理的,我们甚至可以不用做任何操作

这里我们以我最近在使用的那个无需构建即可运行的前端框架为例,从上述步骤中,我们可以知道,最重要的就是要劫持构造函数,在转换为 DOM 时存入我们的 k-v 结构,方便以后使用。

// 劫持构造函数
 const JKL = window.Jinkela
 const storage = {}
 let latest = true
 window.Jinkela = class jkl extends JKL {
 constructor(...args) {
 super(...args)
 const values = 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 = false
 eval(data.content)
 console.log('[HMR] updated JS');
}

     

这样在执行 eval 的过程中就会先记性一遍 DOM 的整理,执行完毕后新的组件就被渲染上去了。

当然,读者可以发现这里有一个前提条件,那就是没有一个内容处于全局作用域,否则就会遇到重复声明的 error 导致热更新失败。

基本上来说是一个非常简单的 Hot Reload,可以完善的地方还是相当多的:

  1. 没有维持连接的心跳包

  2. 频繁对磁盘文件读

  3. 降级 Live Reload 的操作

  4. 目前这种 Hot Reload 只支持单文件组件

  5. 不支持继承

那么,到底能不能有一个通用的支持任意 JS 的 hot reload 呢?目前为止感觉还不能解决重复声明的问题,实际上,webpack 的由 loader 实现大致也是因为各个模块会有其自己的风格,需要单独去处理。

标签: ps

扫描二维码推送至手机访问。

版权声明:本文中部分文字、图片、音频、视频来源于互联网及公开渠道,仅供学习参考,版权归原创者所有! 如侵犯到您的权益,请及时通知我们!我们将在第一时间内删除。

本文链接:http://73ya.com/yinliu/1888.html

分享给朋友:

“webpack热更新原理(http和https的默认端口号)” 的相关文章

短视频推广引流 短视频推广引流的好处

短视频领域现在可以说是有着很大的影响,是流量群体最大的平台。很多人都会想在短视频中座推广引流,那么今天我们就一起来看看关于短视频推广引流,短视频推广引流的好处的相关内容。 短视频推广引流 一、引流短视频的特点。...

制作短视频如何赚钱 制作短视频能赚钱吗

现如今,越来越多的人靠着短视频来赚取收益。短视频的发展越来越快。那么今天我们就一起来看看关于制作短视频如何赚钱,制作短视频能赚钱吗的相关内容。 制作短视频如何赚钱 一、制作短视频的设备准备 一提起制作短视...

抖音短视频带货怎么操作 抖音短视频带货怎么做

相信大家对抖音短视频都不陌生吧,现在抖音带货已经是很常见的事情了。其中是能够赚取收益的,那么应该如何操作呢?今天我们就一起来看看关于抖音短视频带货怎么操作,抖音短视频带货怎么做的相关内容。 抖音短视频带货怎么操作  1、根...

空气炸锅哪个品牌质量比较好性价比高 空气炸锅选购攻略指南

2022空气炸锅十大品牌排行榜 空气炸锅是一种使用起来非常方便的电器,就连厨房小白都可以操作,是利用食物本身的油脂进行“油炸”,但是也没有油炸的过程,还是非常安全的,那么空气炸锅哪个品牌质量比较好呢?现在的空气炸锅品牌非常多,挑起来也非常的麻烦,今天就给大家推荐几个小编觉得不错的品牌,分...

沈阳人口2020总人数口全国排名 2020年沈阳人口总数量介绍

沈阳,是辽宁省省会、副省级市、特大城市,那么,沈阳市人口有多少?据《沈阳市第七次全国人口普查公报》,2020年末,沈阳全市户籍人口762.2万人,市区人口620.2万人,全市常住人口为902.78万人,其中,沈阳铁西区常住人口133.59万人,是沈阳人口最多的区,其次是于洪区、皇姑区,康...

手机图片修改器(专业修改照片的软件排行榜)

相信很多小伙伴都会遇到照片尺寸修改的问题,不清楚照片规定的尺寸是多大,还需要跑到照相馆找人帮忙,非常浪费时间。有没有一款操作方便的修改照片尺寸的工具?今天,小编就以一寸照片为例,给大家介绍怎么在线修改1寸照片尺寸的方法,有需要的小伙伴一起学习下吧! 1、打开压缩图网站,鼠标移入所有功能...