来实现一个 vite ?
基本知识
假设我们有如下两个 JS 文件。
即两个 esm 的模块, 并且 main.mjs 依赖 utils.mjs。
如上代码可以被支持 ESM 的浏览器所识别,但并不意味着其可以直接被运行。 比如我的代码依赖了 npm 包和一些相对路径,这些浏览器是无法识别的。
而 vite 则解决了这个问题。由于 vite 本质还是依赖了浏览器的特性,因此可以直接利用浏览器的诸如缓存的特点来提高性能。
除此之外, 每次修改文件,比如修改上面的 main.mjs 或者 utils.mjs 中的任意一个文件并不会导致“打包”全部文件。这是因为 vite 根本没有打包过程, 而是直接将修改过的文件热更新到浏览器的内存中。
比如,我修改了 main.js,那么就直接发送一个 http 去请求最新的 main.mjs 文件,而 utils.mjs 则可以继续使用浏览器缓存中的内容即可。
我画了一个简单的原理图给大家参考一下。
模块之间的关系如上图所示。并且这个时序指的是更新一个文件之后的更新流程。
我将其分成了若干模块,它们分别是:
- 浏览器。用于处理 ESM
- 文件系统。用于存储源代码文件。
- vite-server。 响应浏览器,并返回内容。这些内容主要是最新的文件系统中的文件,除此外还有注入到 client 中的代码等。
- hrm-sever。用于根据模块的依赖关系确定应该更新的模块,并触发相应的回调函数。
- watcher。 监听文件系统的变更,当文件内容发生变化的时候,通知 hmr-server。之后 hmr-server 再去通过 websocket 通知浏览器获取最新的模块(按需请求)。
如何确定需要更新的模块
我们可以根据 esm 的 import 关系生成一个依赖图。并将图中的所有点都放入一个哈希表中,key 可以是文件的请求路径,value 可以是模块本身,这样就可以根据请求路径在 $O(1)$ 的时间获取到指定的节点。之后我们可以遍历依赖图,并依次发起浏览器的 http 请求获取最新内容,并触发回调函数。
如下图红色的模块被更新,我们通过 $O(1)$ 时间获取到它,然后依次遍历虚线的两个模块,发起请求获取其最新模块内容,最后触发注册到这三个模块上的回调函数即可。
回调函数通过 module.hot.accept 注册,具体参考 hmr 相关文档。
一个更复杂的例子:
之后我会根据这个原理图带大家一步步实现一个 mono-vite(等西法有时间的)。