亮神知识库 亮神知识库
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)

亮神

前程明亮,未来可期
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)
  • babel

  • webpack基础

  • webpack深入

  • Vite

    • 源码
      • 为什么需要vite?
      • 特性
      • 源码浅析
        • 启动时
        • 配置文件
        • 热更新
  • TypeScript

  • 打包工具
  • Vite
0zcl
2025-06-19
目录

源码

# 为什么需要vite?

问题:本地开发时,构建时间长;修改文件后,热更新时间也长。

vite方案解决:利用esbuild,预构建更快;利用浏览支持ES模块,实现按需加载,热更新更快。

# 特性

  • 浏览器支持JS模块 (opens new window)。主流浏览器支持import和export
  • 开发环境使用esbuild (opens new window)预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍
  • 生产环境使用rollup打包。rollup更成熟和灵活。

Vite 只需要在浏览器请求源码时进行转换并按需提供源码。即只在当前屏幕上实际使用时才会被处理。

# 源码浅析

# 启动时

和webpack类型,启动时创建了两个服务。

  1. ws服务。createServer函数中调用createWebSocketServer创建web socket服务器,用于热更新通迅。
  2. 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

# 配置文件

和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

# 热更新

热更新主体流程如下:

  1. 服务端基于 watcher 监听文件改动,根据类型判断更新方式,并编译资源
  2. 客户端通过 WebSocket 监听到一些更新的消息类型和资源路径
  3. 客户端根据消息类型执行热更新逻辑

vite

  • 配置文件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

update

浏览器收到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

为什么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

参考: esbuild (opens new window) Vite 特性和部分源码解析 (opens new window)

编辑 (opens new window)
上次更新: 2025/07/20, 08:30:18
webpack proxy原理
Ts

← webpack proxy原理 Ts→

最近更新
01
2024年
07-20
02
2023年
07-20
03
2022年
07-20
更多文章>
Theme by Vdoing | Copyright © 2025-2025 亮神 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式