源码
# 为什么需要vite?
问题:本地开发时,构建时间长;修改文件后,热更新时间也长。
vite方案解决:利用esbuild,预构建更快;利用浏览支持ES模块,实现按需加载,热更新更快。
# 特性
- 浏览器支持JS模块 (opens new window)。主流浏览器支持
import和export - 开发环境使用esbuild (opens new window)预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍
- 生产环境使用rollup打包。rollup更成熟和灵活。

Vite 只需要在浏览器请求源码时进行转换并按需提供源码。即只在当前屏幕上实际使用时才会被处理。
# 源码浅析
# 启动时
和webpack类型,启动时创建了两个服务。
- ws服务。createServer函数中调用
createWebSocketServer创建web socket服务器,用于热更新通迅。 - http服务。server.listen()会调用
startServer启动node模块的 http server。
// node_module/vite/dist/node/cli.js
const server = await createServer({
// ...
});
await server.listen();
// node_module/vite/dist/node/cli.js
async function createServer(inlineConfig = {}) {
const config = await resolveConfig(inlineConfig, 'serve', 'development');
const httpsOptions = await resolveHttpsConfig(config);
// web socket服务器
const ws = createWebSocketServer(httpServer, config, httpsOptions);
const server = {
// ...
listen(port, isRestart) {
return startServer(server, port, isRestart);
}
}
// ...
return server
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 配置文件
和webpack类似,找配置文件的处理。最终找到项目根目录的vite.config.ts
// node_module/vite/dist/node/chunks/dep-55830a1a.js
async function loadConfigFromFile(configEnv, configFile, configRoot = process.cwd(), logLevel) {
// ...
if (!userConfig) {
// 2. if we reach here, the file is ts or using es import syntax, or
// the user has type: "module" in their package.json (#917)
// transpile es import syntax to require syntax using rollup.
// lazy require rollup (it's actually in dependencies)
const bundled = await bundleConfigFile(resolvedPath);
dependencies = bundled.dependencies;
userConfig = await loadConfigFromBundledFile(resolvedPath, bundled.code);
debug$1(`bundled config file loaded in ${getTime()}`);
}
// ...
return {
path: normalizePath$4(resolvedPath),
config,
dependencies
};
}
// node_module/vite/dist/node/chunks/dep-55830a1a.js
async function resolveConfig(inlineConfig, command, defaultMode = 'development') {
// ...
if (configFile !== false) {
const loadResult = await loadConfigFromFile(configEnv, configFile, config.root, config.logLevel);
if (loadResult) {
config = mergeConfig(loadResult.config, config);
configFile = loadResult.path;
configFileDependencies = loadResult.dependencies;
}
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 热更新
热更新主体流程如下:
- 服务端基于 watcher 监听文件改动,根据类型判断更新方式,并编译资源
- 客户端通过 WebSocket 监听到一些更新的消息类型和资源路径
- 客户端根据消息类型执行热更新逻辑

- 配置文件
vite.config.ts发生修改时,会触发服务重启 - html 文件更新时,ws发送full-reload,将会触发页面的重新加载
- 热更新,ws发送update标志
// node_module/vite/dist/node/chunks/dep-55830a1a.js
async function handleHMRUpdate(file, server) {
const isConfig = file === config.configFile;
if (isConfig || isConfigDependency || isEnv) {
// auto restart server
await restartServer(server);
return;
}
// ...
// html file cannot be hot updated
if (file.endsWith('.html')) {
ws.send({
type: 'full-reload',
path: config.server.middlewareMode
? '*'
: '/' + normalizePath$4(path__default.relative(config.root, file))
});
}
updateModules(shortFile, hmrContext.modules, timestamp, server);
}
function updateModules(file, modules, timestamp, { config, ws }) {
const updates = [];
updates.push(...[...boundaries].map(({ boundary, acceptedVia }) => ({
type: `${boundary.type}-update`,
timestamp,
path: boundary.url,
acceptedPath: acceptedVia.url
})));
// ...
if (needFullReload) {
ws.send({
type: 'full-reload'
});
}
else {
ws.send({
type: 'update',
updates
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

浏览器收到ws消息后。
- 收到full-reload标志时,调用location.reload()重载页面
- 收到update标志时,使用import加载更新后的模块
async function handleMessage(payload) {
switch (payload.type) {
case 'update':
payload.updates.forEach((update) => {
if (update.type === 'js-update') {
queueUpdate(fetchUpdate(update))
}
}
case 'full-reload':
// ...
location.reload();
}
break;
}
}
async function fetchUpdate({ path, acceptedPath, timestamp }: Update) {
const newMod = await import(
/* @vite-ignore */
base +
path.slice(1) +
`?import&t=${timestamp}${query ? `&${query}` : ''}`
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
为什么watcher.on('change', async (file) => {})能监听到文件变化?
本质上和webpack是一样的,利用eventEmitter。watcher._watched是一个收集文件的Map。watcher继承了eventEmitter, 因此watcher.on能监听变化。不过是在哪里emit的??源码中没找到...
class FSWatcher extends EventEmitter$2 {
// Not indenting methods for history sake; for now.
constructor(_opts) {
super();
/** @type {Map<String, DirEntry>} */
this._watched = new Map();
}
}
const watch = (paths, options) => {
const watcher = new FSWatcher(options);
watcher.add(paths);
return watcher;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
参考: esbuild (opens new window) Vite 特性和部分源码解析 (opens new window)
编辑 (opens new window)
上次更新: 2025/07/20, 08:30:18