We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我们下面讨论的HMR都是基于vite自身实现的一套HMR系统。 vite实现的HMR是根据 ESM HMR 规范 来实现的。
HMR
vite
HMR:Hot Module Reload模块热更新。 之前当我们在编辑器中更新代码时,会触发浏览器的页面刷新,但是这个刷新是全量刷新,相当于CMD+R。这时页面的状态会被重置掉,总之体验不好。 而模块热更新就是为了解决这样的问题,只是刷新我们编辑的代码所对应的模块,并且能保持页面的状态。
Hot Module Reload
CMD+R
可以看到这里我们在编辑代码时,下面count的状态是保存了的。只是热更新了上面的文字部分的模块。
count
其实每个技术的诞生,都是为了解决之前所凸显出来的问题。HMR 也是如此,其实在上面也已经说了原因。 这里再来总结一下:为什么需要 HMR?
vite中实现的HMR系统其实是对ESM HMR规范中的API进行了一层封装。vite会主动监听文件的变化,然后触发对应的API,来实现模块的热更新。 所以首先我们来简单了解一下这套规范中的API
ESM HMR
API
hmr的API都注入到了import.meta的hot中。 我们访问的时候只需要import.meta.hot.[name]即可
hmr
import.meta
hot
import.meta.hot.[name]
import.meta是浏览器中内置的一个对象。【MDN】
interface ImportMeta { readonly hot?: { readonly data: any // ======触发更新===== accept(): void // accept(cb: (mod: any) => void): void accept(dep: string, cb: (mod: any) => void): void accept(deps: string[], cb: (mods: any[]) => void): void // ================== prune(cb: () => void): void dispose(cb: (data: any) => void): void decline(): void invalidate(): void // =====监听hmr事件==== on(event: string, cb: (...args: any[]) => void): void // =================== } }
accept翻译过来就是接受。而在hmr中他也是这个意思:接受此次热更新,而接受热更新的模块被称为HMR边界 当我们在文件中加入这行代码的时候,就是手动开启该文件模块的热更新。 当这个文件中的代码产生更新时,就会接收此次热更新的结果。
accept
if (import.meta.hot) { import.meta.hot.accept((mod) => { console.log(mod, '==') }) }
:::danger accept中的mod就是更新之后的模块中所导出的内容。 ::: 比如我们的文件是下面这样,导出了render和other:
mod
render
other
export const render = () => { // ... } export const other = () => { //... } if (import.meta.hot) { import.meta.hot.accept((mod) => { console.log(mod, '==') }) }
那么当我们在这个文件中更新代码,接受热更新时此时mod中就是: 如果我们需要接受其中一个导出模块的更新,那么直接调用mod.render()或者mod.other()即可在页面上更新到最新的内容。
mod.render()
mod.other()
如果你的文件中导出方式是默认导出export default xxx,那么mod中就是mod.default
export default xxx
mod.default
在上面的代码中,我们是向accept中传递了一个回调函数来主动触发热更新模块中的函数。因为我们这个文件中只是声明了render、other函数,并没有执行,所以需要在accpet的回调中手动触发才可以 其实有些情况下也不用传回调函数。accept会把当前变更的文件中的最新内容执行一遍。就比如我们这个文件就是一个可执行文件(类似自执行函数),当我们import这个文件的时候,文件里的代码就会执行,例如下面的情况:
accpet
import
// render.ts const render = () => { const app = document.querySelector<HTMLDivElement>('#app')! app.innerHTML = ` <h1>Hello Vite12</h1> <p id="p">是是是</p> ` } render() if (import.meta.hot) { import.meta.hot.accept() } // main.ts import './render.tx'’
在render文件执行执行了render函数,这时accept就会重新执行这个文件,也就理所当然的触发了render函数。这时就不需要我们向accpet传递回调函数了。
accept方法中也可以接收一个dep参数,也就是当前页面热更新时所依赖的子模块的路径。 这个dep参数,可以是一个单独字符串,也可以是一个字符串数组,当是数组时说明依赖多个子模块
dep
//main.ts import { render } from './render' import { initsate } from './state' render() initsate() if (import.meta.hot) { import.meta.hot.accept('./render.ts', (mod) => { console.log(mod, '==') mod?.render() }) }
main模块依赖render文件 当render文件变更时,会接收热更新 因为此时没有依赖state文件,所以当state文件发生变更时会**reload page**,而不会热更新。 因为此时热更新的边界仅仅是render模块,只有render模块中的变更才会触发main的热更新
main
state
**reload page**
//main.ts import { 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模块没有更新,符合预期。
mod1
undefined
这个函数就是比较简单。就是在新模块更新前 旧模块销毁时的钩子。用来清理掉旧模块中的一些副作用。
const timerId = setInterval(() => { countEle.innerText = Number(countEle.innerText) + 1 + '' }, 1000) if (import.meta.hot) { import.meta.hot.dispose((data) => { // 清理副作用 clearInterval(timerId) }) }
在我们需要 hmr 的模块中如果有定时器之类的操作,我们热更新后如果不提前销毁定时器,就会重复执行定时,那么可能会出现意想不到效果。
监听自定义 HMR 事件。 自定义 HMR 事件,是在服务端定义发送的。在 vite 中,我们可以在插件中完成这件事。 vite插件中提供了handleHotUpdate
handleHotUpdate
// vite-plugin.tx // 省略其他代码 handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'xxx-file-change', // 自定义事件名称 data: {} // 携带的信息 }) return [] } // client import.meta.hot.on('xxx-file-change', (payload) => { console.log(payload) })
sanyuan0704/island.js#79 有时自定义 hmr 事件,没有触发页面更新。我们可以利用监听自定义事件,来主动触发页面的rerender
rerender
该属性用来共享同一个模块中更新前后的数据。 在这里面绑定的数据,不会被hmr影响或重置。
import.meta.hot.data.count = 1
表示此模块不可热更新,如果在传播 HMR 更新时遇到此模块,浏览器应该执行完全重新加载。
重新加载页面。
:::tip 请看下一篇文章 :::
The text was updated successfully, but these errors were encountered:
No branches or pull requests
What(HMR 是什么?)
HMR
:Hot Module Reload
模块热更新。之前当我们在编辑器中更新代码时,会触发浏览器的页面刷新,但是这个刷新是全量刷新,相当于
CMD+R
。这时页面的状态会被重置掉,总之体验不好。而模块热更新就是为了解决这样的问题,只是刷新我们编辑的代码所对应的模块,并且能保持页面的状态。
Why(为什么需要 HMR?)
其实每个技术的诞生,都是为了解决之前所凸显出来的问题。HMR 也是如此,其实在上面也已经说了原因。
这里再来总结一下:为什么需要 HMR?
How(怎么使用 HMR?)
vite
中实现的HMR
系统其实是对ESM HMR
规范中的API
进行了一层封装。vite
会主动监听文件的变化,然后触发对应的API
,来实现模块的热更新。所以首先我们来简单了解一下这套规范中的
API
API
hmr
的API
都注入到了import.meta
的hot
中。我们访问的时候只需要
import.meta.hot.[name]
即可accept(cb)
accept
翻译过来就是接受。而在hmr
中他也是这个意思:接受此次热更新,而接受热更新的模块被称为HMR
边界当我们在文件中加入这行代码的时候,就是手动开启该文件模块的热更新。
当这个文件中的代码产生更新时,就会接收此次热更新的结果。
:::danger
accept
中的mod
就是更新之后的模块中所导出的内容。:::
比如我们的文件是下面这样,导出了
render
和other
:那么当我们在这个文件中更新代码,接受热更新时此时
![image.png](https://camo.githubusercontent.com/07f07dafb42b113fec58fc00fd41a6151041b06aff4e6c4fe22c748134aebe83/68747470733a2f2f63646e2e6e6c61726b2e636f6d2f79757175652f302f323032322f706e672f323730353835302f313636353437373934333236322d30636536646461302d393839382d343762342d613166652d6230316361613630343536302e706e67)
mod
中就是:如果我们需要接受其中一个导出模块的更新,那么直接调用
mod.render()
或者mod.other()
即可在页面上更新到最新的内容。在上面的代码中,我们是向
accept
中传递了一个回调函数来主动触发热更新模块中的函数。因为我们这个文件中只是声明了render
、other
函数,并没有执行,所以需要在accpet
的回调中手动触发才可以其实有些情况下也不用传回调函数。
accept
会把当前变更的文件中的最新内容执行一遍。就比如我们这个文件就是一个可执行文件(类似自执行函数),当我们import
这个文件的时候,文件里的代码就会执行,例如下面的情况:accept(dep, cb)
accept
方法中也可以接收一个dep
参数,也就是当前页面热更新时所依赖的子模块的路径。这个
dep
参数,可以是一个单独字符串,也可以是一个字符串数组,当是数组时说明依赖多个子模块dispose()
这个函数就是比较简单。就是在新模块更新前 旧模块销毁时的钩子。用来清理掉旧模块中的一些副作用。
on(event,cb)
监听自定义
HMR
事件。自定义 HMR 事件,是在服务端定义发送的。在 vite 中,我们可以在插件中完成这件事。
vite
插件中提供了handleHotUpdate
data
该属性用来共享同一个模块中更新前后的数据。
在这里面绑定的数据,不会被
hmr
影响或重置。decline()
表示此模块不可热更新,如果在传播 HMR 更新时遇到此模块,浏览器应该执行完全重新加载。
invalidate()
重新加载页面。
下回书
:::tip
请看下一篇文章
:::
The text was updated successfully, but these errors were encountered: