推广 热搜: 终于知道  2022  1*1  2023 

「重磅消息」webpack热更新原理(热更新和冷更新区别)

   日期:2023-05-16     来源:https://www.xzqncm.com/    作者:www.xzqncm.com    浏览:0    评论:0    
核心提示:我们大致了解了webpack HMR 原理。可以看出以下几点核心思想:1、监听文件变化2、服务器与客户端通信3、替换流程4、降级操作当然

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

1、监听文件变化

2、服务器与客户端通信

3、替换流程

4、降级操作

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

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

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

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

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

4、(模块处理失败,降级为 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 = 'http://www.yujujie.cn/shbk/static'

const watcher = chokidar.watch('http://www.yujujie.cn/shbk/static', {

ignored: /node_modules|.git|[/]http://www.yujujie.cn/shbk/

})

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 = ` `

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('', `${injectedData}`)

if (file.endsWith('.css')) ctx.type = 'text/css'

ctx.body = body

next()

})

app.listen(3001)

console.log('listen on port 3001')

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

 

WebSocket实现简单的webpack HMR(热更新)效果

 

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

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

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

通常来说,我们要覆盖 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');

}

 

WebSocket实现简单的webpack HMR(热更新)效果

 

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

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

读取文件

构造语法树

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

通信将差异传给客户端

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

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

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', '

ink href="http://www.yujujie.cn/shbk/' + data.content + '" rel="stylesheet" /> ')

console.log('[HMR] updated CSS');

}

 

WebSocket实现简单的webpack HMR(热更新)效果

 

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

最大的难点在于 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 结构,方便以后使用。

// 劫持构造函数

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');

}

 

WebSocket实现简单的webpack HMR(热更新)效果

 

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

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

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

没有维持连接的心跳包

频繁对磁盘文件读

降级 Live Reload 的操作

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

不支持继承

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


这篇文章将帮助你在购买适合您手腕的手表时需要考虑的5件事情。手表和手腕的尺寸比例配搭很重要。一般来说,如果您手腕偏细,那么尺寸较小的手表会比较适合您;如果您手腕偏粗,那么尺寸较大一点的手表就更适合您。那么具体什么尺寸手表和多粗的手臂更搭更协调?我们在这把它分解成5个关键元素:1.表壳直径,表壳直径是最明显和最显著的比例因素。男士手表的尺寸通常在38毫米到46毫米之间,任何超过46毫米的手表都会显得太大,而且这样尺寸的通常都是为华而不实的的时尚手表所设计。38毫米以下的手表多倾向于珠宝装饰,更容偏向女性尺寸的手表。那么你怎么知道什么尺码适合您呢?
 
打赏
 
更多>同类资讯
0相关评论

推荐图文
推荐资讯
点击排行

网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  苏ICP备19027764号-1