Vite 是如何做热更新的
假如我们自己做热更新HMR(Hot Module Reload/Replacement)
,我们大体的思路是什么呢,很常见得一个场景,在项目中写代码的时候,修改一个元素的样式,ctrl/commad+s
保存后,修改的样式在页面中发生了变化,这个场景,我们可以捕获到两个动作
- 保存文件之后文件内容发生了变化
- 修改的内容被
replace/reload
页面重新渲染了
我们从这两方面看Vite是怎么做的
监听文件变动
在Node.js
的FS
模块中,有fs.watch
和fs.watchfile
两个监听文件夹和文件变化的API
,我们可以通过这两个API
实现我们的思路的第一步,但是这里边有些坑,在这个chokidar
库中有说明
Node.js
fs.watch
:
- Doesn’t report filenames on MacOS.
- Doesn’t report events at all when using editors like Sublime on MacOS.
- Often reports events twice.
- Emits most changes as
rename
.- Does not provide an easy way to recursively watch file trees.
- Does not support recursive watching on Linux.
Node.js
fs.watchFile
:
- Almost as bad at event handling.
- Also does not provide any recursive watching.
- Results in high CPU utilization.
三个字总结就是有问题,chokidar
解决了以上问题,所以文件监听的任务就交给chokidar
了,下边的代码是vite2.x
中通过chokidar
监听变动的代码
const watcher = chokidar.watch(path.resolve(root), {
ignored: [
'**/node_modules/**',
'**/.git/**',
...(Array.isArray(ignored) ? ignored : [ignored])
],
ignoreInitial: true,
ignorePermissionErrors: true,
disableGlobbing: true,
...watchOptions
}) as FSWatcher
知道了文件的变动,如何把变动的文件更新到页面呢
更新文件变动
更新文件到页面,需要我们通过websocket
对server端和client端建立链接,websocket逻辑在packages\vite\src\node\server\ws.ts
里
Server端
通过监听change事件,通过handleHMRUpdate
方法判断不同的type去执行不同的逻辑(通过ws
发送消息)
watcher.on('change', async (file) => {
file = normalizePath(file)
if (file.endsWith('/package.json')) {
return invalidatePackageData(packageCache, file)
}
// invalidate module graph cache on file change
moduleGraph.onFileChange(file)
if (serverConfig.hmr !== false) {
try {
await handleHMRUpdate(file, server)
} catch (err) {
ws.send({
type: 'error',
err: prepareError(err)
})
}
}
})
// handleHMRUpdate 方法里的部分代码
...
updates.push(
...[...boundaries].map(({ boundary, acceptedVia }) => ({
type: `${boundary.type}-update` as Update['type'],
timestamp,
path: boundary.url,
acceptedPath: acceptedVia.url
}))
)
...
在packages\vite\types\hmrPayload.d.ts
路径下定义的type类型
Client端
在client端监听socket消息,代码如下
// Listen for messages
socket.addEventListener('message', async ({ data }) => {
handleMessage(JSON.parse(data))
})
下图在handleMessage
方法中,对应server端的类型,被折叠的代码中有一个fetchUpdate
方法 里边的逻辑是热更的逻辑
可以在packages\vite\src\client\client.ts
查看详细的逻辑
总结
- 通过
chokidar
监听文件变动,变动的类型不同执行的方式也不一样 - 通过websocket,建立server端和client端通信