從服務(wù)端我們了解到是接住websocket與客戶端進(jìn)行通信酝枢,下面我們來看一下客戶端的代碼:
第1步
初始化websocket實(shí)例,socket協(xié)議以來當(dāng)前的協(xié)議,如果是https那就使用wss,否則使用ws。子協(xié)議名稱使用vite-hmr
// packages/vite/src/client/client.ts
const socketProtocol =
__HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws')
const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`
const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr')
const base = __BASE__ || '/'
第2步
添加socket消息監(jiān)聽事件质礼,消息使用JSON.parse解析
// packages/vite/src/client/client.ts
socket.addEventListener('message', async ({ data }) => {
handleMessage(JSON.parse(data))
})
async function handleMessage(payload: HMRPayload) {
switch (payload.type) {
case 'connected':
console.log(`[vite] connected.`)
// proxy(nginx, docker) hmr ws maybe caused timeout,
// so send ping package let ws keep alive.
setInterval(() => socket.send('ping'), __HMR_TIMEOUT__)
break
case 'update':
notifyListeners('vite:beforeUpdate', payload)
// if this is the first update and there's already an error overlay, it
// means the page opened with existing server compile error and the whole
// module script failed to load (since one of the nested imports is 500).
// in this case a normal update won't work and a full reload is needed.
if (isFirstUpdate && hasErrorOverlay()) {
window.location.reload()
return
} else {
clearErrorOverlay()
isFirstUpdate = false
}
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
let { path, timestamp } = update
path = path.replace(/\?.*/, '')
// 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.
const el = (
[].slice.call(
document.querySelectorAll(`link`)
) as HTMLLinkElement[]
).find((e) => e.href.includes(path))
if (el) {
const newPath = `${path}${
path.includes('?') ? '&' : '?'
}t=${timestamp}`
el.href = new URL(newPath, el.href).href
}
console.log(`[vite] css hot updated: ${path}`)
}
})
break
case 'custom': {
notifyListeners(payload.event as CustomEventName<any>, payload.data)
break
}
case 'full-reload':
notifyListeners('vite:beforeFullReload', payload)
if (payload.path && payload.path.endsWith('.html')) {
// if html file is edited, only reload the page if the browser is
// currently on that page.
const pagePath = location.pathname
const payloadPath = base + payload.path.slice(1)
if (
pagePath === payloadPath ||
(pagePath.endsWith('/') && pagePath + 'index.html' === payloadPath)
) {
location.reload()
}
return
} else {
location.reload()
}
break
case 'prune':
notifyListeners('vite:beforePrune', payload)
// After an HMR update, some modules are no longer imported on the page
// but they may have left behind side effects that need to be cleaned up
// (.e.g style injections)
// TODO Trigger their dispose callbacks.
payload.paths.forEach((path) => {
const fn = pruneMap.get(path)
if (fn) {
fn(dataMap.get(path))
}
})
break
case 'error': {
notifyListeners('vite:error', payload)
const err = payload.err
if (enableOverlay) {
createErrorOverlay(err)
} else {
console.error(
`[vite] Internal Server Error\n${err.message}\n${err.stack}`
)
}
break
}
default: {
const check: never = payload
return check
}
}
}
針對(duì)收到不同的通信類型來張表格梳理下:
類型 | 說明 |
---|---|
connected | 提示連接中,等待一段時(shí)間后發(fā)送ping |
update | 更新分為js update和cssupdate织阳, 遍歷payload.updates的所有文件 |
custom | 無 |
full-reload | location reload |
prune | 裁剪:payload.paths的每個(gè)路徑都拿取剪裁函數(shù)執(zhí)行 |
error | 獲取錯(cuò)誤并借助console.error展示 |
default | 返回payload |