vite 客户端使用来响应服务端文件改动, 最终客户端的代码通过注入的方向,注入到index.html中,实现热更新功能
首先客户端和服务端通信使用的是websocket功能实现双向通信
相关WebSocket的源码 🔗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
letsocket: WebSockettry{socket=newWebSocket(`${socketProtocol}://${socketHost}`,'vite-hmr')// Listen for messages
socket.addEventListener('message',async({data})=>{handleMessage(JSON.parse(data))})// ping server
socket.addEventListener('close',async({wasClean})=>{if(wasClean)returnconsole.log(`[vite] server connection lost. polling for restart...`)awaitwaitForSuccessfulPing()location.reload()})}catch(error){console.error(`[vite] failed to connect to websocket (${error}). `)}
if(payload.path&&payload.path.endsWith('.html')){// if html file is edited, only reload the page if the browser is
// currently on that page.
constpagePath=decodeURI(location.pathname)constpayloadPath=base+payload.path.slice(1)if(pagePath===payloadPath||payload.path==='/index.html'||(pagePath.endsWith('/')&&pagePath+'index.html'===payloadPath)){location.reload()}return}else{location.reload()}
payload.updates.forEach((update)=>{if(update.type==='js-update'){queueUpdate(fetchUpdate(update))}else{// css-update
// this is only sent when a css file referenced with <link> is updated
const{path,timestamp}=updateconstsearchUrl=cleanUrl(path)// can't use querySelector with `[href*=]` here since the link may be
// using relative paths so we need to use link.href to grab the full
// URL for the include check.
constel=Array.from(document.querySelectorAll<HTMLLinkElement>('link')).find((e)=>cleanUrl(e.href).includes(searchUrl))if(el){constnewPath=`${base}${searchUrl.slice(1)}${searchUrl.includes('?')?'&':'?'}t=${timestamp}`// rather than swapping the href on the existing tag, we will
// create a new link tag. Once the new stylesheet has loaded we
// will remove the existing link tag. This removes a Flash Of
// Unstyled Content that can occur when swapping out the tag href
// directly, as the new stylesheet has not yet been loaded.
constnewLinkTag=el.cloneNode()asHTMLLinkElementnewLinkTag.href=newURL(newPath,el.href).hrefconstremoveOldEl=()=>el.remove()newLinkTag.addEventListener('load',removeOldEl)newLinkTag.addEventListener('error',removeOldEl)el.after(newLinkTag)}console.log(`[vite] css hot updated: ${searchUrl}`)}})
asyncfunctionfetchUpdate({path,acceptedPath,timestamp}:Update){// 获取热更新模块
constmod=hotModulesMap.get(path)// 不存在,则推出
if(!mod){// In a code-splitting project,
// it is common that the hot-updating module is not loaded yet.
// https://github.com/vitejs/vite/issues/721
return}// 模块容器
constmoduleMap=newMap()constisSelfUpdate=path===acceptedPath// make sure we only import each dep once
constmodulesToUpdate=newSet<string>()if(isSelfUpdate){// self update - only update self
// 自身更新
modulesToUpdate.add(path)}else{// dep update
// 更新依赖
for(const{deps}ofmod.callbacks){// 循环更新依赖
deps.forEach((dep)=>{if(acceptedPath===dep){modulesToUpdate.add(dep)}})}}// determine the qualified callbacks before we re-import the modules
constqualifiedCallbacks=mod.callbacks.filter(({deps})=>{returndeps.some((dep)=>modulesToUpdate.has(dep))})// promise.all 更新数据
awaitPromise.all(Array.from(modulesToUpdate).map(async(dep)=>{constdisposer=disposeMap.get(dep)if(disposer)awaitdisposer(dataMap.get(dep))const[path,query]=dep.split(`?`)try{// 获取新的模块数据
constnewMod=awaitimport(/* @vite-ignore */base+path.slice(1)+`?import&t=${timestamp}${query?`&${query}`:''}`)// 添加到模块容器中
moduleMap.set(dep,newMod)}catch(e){warnFailedFetch(e,dep)}}))return()=>{for(const{deps,fn}ofqualifiedCallbacks){fn(deps.map((dep)=>moduleMap.get(dep)))}constloggedPath=isSelfUpdate?path:`${acceptedPath} via ${path}`console.log(`[vite] hot updated: ${loggedPath}`)}}