Vite 源码(七)Vite 的热更新原理


theme: cyanosis

highlight: monokai

前置

经过前面几篇铺垫之后,接下来看下 Vite 的热更新原理,先看下前置流程图

接下来会详细分析下上图的过程

demo

假设main.ts文件如下

typescript import a from './a' console.log(a) if(import.meta.hot){ import.meta.hot.accept('./a', a => { console.log('hmr', a) }) }

请求 main.ts

请求main.ts时,如果main.ts中存在import.meta.hot.accept。经importAnalysisPlugin编译后,会向这个文件中添加一段代码

typescript import { createHotContext as __vite__createHotContext } from "/@vite/client"; import.meta.hot = __vite__createHotContext(url/* 从项目根路径查找的当前文件的绝对路径 */); 当客户端执行main.ts时,调用/@vite/clientcreateHotContext函数

监听服务器返回的消息

/@vite/client路径解析之后对应源码的位置就是/src/client/client.ts

``typescript const socketProtocol = __HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws') const socketHost =${ HMRHOSTNAME || location.hostname}:${ HMRPORT} // 创建 WebSocket 对象 const socket = new WebSocket(${socketProtocol}://${socketHost}`, 'vite-hmr') const base = BASE || '/'

// Listen for messages socket.addEventListener('message', async ({ data }) => {}) `` 首先会创建一个WebSocket对象,并注册监听事件,也就是说当服务器通过WebSocket`返回信息时,会被这个事件捕获。

还会定义一些变量,这些变量如下 ```typescript /* * 存储的是被请求文件的 import.meta.hot.accept * key: 当前文件地址 * value: { id: 文件地址, callbacks: [{ deps: 被监听的文件路径数组, fn: 定义的回调函数 }] } */ const hotModulesMap = new Map() /* * 存储的是所有被请求文件的 import.meta.hot.dispose * key: 当前文件地址 * value: 定义的回调函数 / const disposeMap = new Map>() /* * 存储的是所有被请求文件的 import.meta.hot.prune * key: 当前文件地址 * value: 定义的回调函数 / const pruneMap = new Map>() /* * 存储的是所有被请求文件的 import.meta.hot.data * 对象在同一个更新模块的不同实例之间持久化。它可以用于将信息从模块的前一个版本传递到下一个版本。 * key: 当前文件地址 * value: 是一个对象,持久化的信息 / const dataMap = new Map() /* * 存储的是被请求文件监听的 hmr 钩子 * key: 事件名称 * value: 事件对应回调函数的数组 / const customListenersMap = new Map* * 存储的是被请求文件监听的 hmr 钩子 * key: 当前文件地址 * value: 是一个 Map,Map 内的 key 是事件名,valye 是一个回调函数数组 */ const ctxToListenersMap = new Map

export const createHotContext = (ownerPath: string) => {} `` customListenersMapctxToListenersMap`在最后介绍自定义钩子时会介绍

创建 import.meta.hot 对象

继续向下,导出 createHotContext 函数

上面说过,对于存在import.meta.hot.accept的模块会执行这个函数,并传入当前文件的绝对路径。 typescript import { createHotContext as __vite__createHotContext } from "/@vite/client"; import.meta.hot = __vite__createHotContext(url/* 从项目根路径查找的当前文件的绝对路径 */);

createHotContext函数如下 ```typescript // ownerPath 当前文件的路径 export const createHotContext = (ownerPath: string) => { // 如果 dataMap 中没有当前路径,则添加到 dataMap 中 if (!dataMap.has(ownerPath)) { dataMap.set(ownerPath, {}) }

// when a file is hot updated, a new context is created
// clear its stale callbacks
const mod = hotModulesMap.get(ownerPath)
if (mod) {
    // 清空 cb
    mod.callbacks = []
}

// 清除过时的自定义事件监听器(最后介绍自定义钩子时会介绍)
// ...

const newListeners = new Map()
ctxToListenersMap.set(ownerPath, newListeners)

function acceptDeps(){}

const hot = {
    get data() {},
    accept(deps: any, callback?: any) {},
    acceptDeps() {},
    dispose(cb: (data: any) => void) {},
    prune(cb: (data: any) => void) {},
    // TODO
    decline() {},
    invalidate() {},
    on: (event: string, cb: (data: any) => void) => {},
}

return hot

} `` 这个函数最主要的作用就是定义一个hot对象,并返回这个对象。返回的对象会赋值给模块的import.meta.hot`

总结下createHotContext函数的作用: - 创建持久化数据对象,并添加到dataMap中 - 清空hotModulesMap内当前路径绑定的依赖回调(accept函数的参数) - 清除过时的自定义事件监听器 - 给当前路径创建自定义事件回调的容器,并放到ctxToListenersMap

执行import.meta.hot.accept方法

在这里主要分析下accept方法的作用,其他方法都比较简单这里就不赘述了,可以直接去源码中看。

typescript accept(deps: any, callback?: any) { if (typeof deps === 'function' || !deps) { // self-accept: hot.accept(() => {}) acceptDeps([ownerPath], ([mod]) => deps && deps(mod)) } else if (typeof deps === 'string') { // explicit deps acceptDeps([deps], ([mod]) => callback && callback(mod)) } else if (Array.isArray(deps)) { acceptDeps(deps, callback) } else { throw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值