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

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

admin2年前 (2022-10-10)引流516

我们大致了解了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

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

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

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

分享给朋友:

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

抖音视频怎么推广 抖音视频怎么推广宣传

大家拍完抖音后是不是流量都不高呢,那么这就需要做到推广宣传了。今天我们就一起来看看关于抖音视频怎么推广,抖音视频怎么推广宣传的相关内容。 抖音视频怎么推广  1、通过开通抖音信息流广告账户进行推广 抖音信...

抖音视频制作 抖音视频制作用什么软件

现在,短视频行业是很有发展的,也有不少人想要学习如何制作短视频。那么今天我们就一起来看看关于抖音视频制作,抖音视频制作用什么软件的相关内容。 抖音视频制作   剪映 是抖音官方推出的剪辑软件,第一大优...

免费的短视频素材网站推荐,音频、视频样样都有

找素材是不是最头疼,作为一个短视频的原创作者来说,每天不是在想剧情的路上,就是在找素材的路上。作为一个短视频作者真的需要身兼多项才艺。那么免费的短视频素材网站就推荐给大家了,音频视频都有,等你来看。废话不多说,直接上干货。 免费的短视频素材网站推荐 1.Bensound Bensou...

剪辑短视频月入过万,是真的吗?

可能你也发现了这年头靠自己每日上班存钱真的是太难了。大多数人都会考虑发展副业,那么本身休息时间就不是很长,那么如何依靠闲暇时间来做副业,剪辑短视频月入过万?是真的吗?接下来就跟小编一起来看看吧。 全职妈妈:老公失业,房贷3000,车贷1300,还有两個儿子上學,没工作到里去找钱! 99...

大年初三的风俗和禁忌图片(大年初三禁忌小常识分享)

大年初三,又到了。据说,正月初三日为天庆节,后来称小年朝。是仅次于大年初一的天庆节。 在民间,当时有很多禁忌。当然,现在已经不合时宜,甚至已经抛弃,大家就当作一种传统文化知识来看看吧。那具体有哪些传统禁忌呢? 1、大年初三不拜年,防止口舌。 主要因为大年初三,是赤狗日。是一个不是十分...

手机看书软件排行榜第一名(永久免费的看书神器)

互联网的快速发展大大的方便了我们的学习和生活,尤其是对于书迷们来说,各种看书软件的出现让我们只要拥有一部手机就能随时随地看书,那么看书软件哪些好呢?今天小编为大家盘点了看书软件排行榜前十名,一起来看看吧! 1、掌阅iReader 掌阅是北京掌阅科技有限公司开发的一款手机阅读软件,用户...