Skip to content

启动流程详解

一个程序的启动流程,往往暴露它最核心的设计决策。Claude Code 的启动链路走下来,你会发现这帮人对性能优化是认真的。

启动链路总览

从你敲下 claude 到看见输入框,经过 3 个关键文件:

用户执行 claude
    |
cli.tsx (303行)     -- 巨型路由器,所有模块延迟加载
    |
main.tsx (4684行)   -- Commander.js 选项解析 + 迁移
    |
init.ts (309行)     -- 多步骤初始化,memoize 保证只跑一次
    |
REPL.tsx            -- React 组件挂载,交互循环开始

第一站:cli.tsx -- 命令行路由

cli.tsxmain() 函数本质上是一个巨型 switch-case。但有个关键设计:所有依赖都用动态 import()

为什么?因为 Claude Code 有十几种运行模式——普通 REPL、管道模式、MCP 服务器、Bridge、Daemon……每种模式需要的模块完全不同。如果在入口全部 import,启动时间直接爆炸。

路由表(部分)

条件目标说明
--version直接输出零模块加载快速路径
--mcpMCP 服务器feature gate 控制
-p / --pipe管道模式一次性查询
bridge / remoteBridge 模块远程协作
daemon守护进程后台运行
默认main.tsx交互式 REPL

这种路由设计我在很多 CLI 工具里见过,但做到这个粒度的不多。16+ 个 feature-gated 快速路径,每个都是独立的动态 import。

第二站:main.tsx -- 配置解析与迁移

4684 行的 main.tsx 是主力文件,干两件事:

1. Commander.js 选项解析

大约 50 个 CLI 选项:

typescript
program
  .option('-p, --pipe', '管道模式')
  .option('--model <model>', '模型选择')
  .option('--max-turns <n>', 'Agent 最大轮次')
  .option('--permission-mode <mode>', '权限模式')
  .option('--resume <session>', '恢复会话')
  .option('--output-format <fmt>', '输出格式')
  .option('--verbose', '详细日志')

2. 迁移系统

版本号已经到 11 了,说明经历过不少次配置格式变更:

typescript
const CURRENT_MIGRATION_VERSION = 11

迁移包括 9 个同步迁移、2 个条件迁移、1 个异步迁移。用户升级后旧配置自动迁移,不用手动改。

第三站:init.ts -- 初始化序列

init() 被 memoize 包裹,保证只执行一次:

1.  enableConfigs()              -- 启用配置系统
2.  applySafeConfigEnvVars()     -- 安全环境变量
3.  setupGracefulShutdown()      -- 注册 SIGINT/SIGTERM 清理
4.  初始化事件日志 + GrowthBook   -- 特性门控就绪
5.  populateOAuthAccountInfo()   -- OAuth 账户信息
6.  initJetBrainsDetection()     -- IDE 检测
7.  detectCurrentRepository()    -- Git 仓库检测
8.  initRemoteManagedSettings()  -- 远程配置加载
9.  recordFirstStartTime()       -- 首次启动时间
10. configureGlobalMTLS()        -- mTLS 证书
11. configureGlobalAgents()      -- 代理配置
12. preconnectAnthropicApi()     -- API 预连接

注意第 12 步——在用户还没输入任何东西时,就先和 API 建立 TCP 连接。等用户真正发消息时,少一次握手延迟。

延迟遥测

OpenTelemetry SDK 约 400KB,等用户接受信任对话框后才加载。遥测不影响核心功能,没必要拖慢启动。

第四站:REPL.tsx -- 组件树

jsx
<REPL>
  <App>
    <FpsMetricsProvider>
      <StatsProvider>
        <AppStateProvider>
          <FullscreenLayout>
            <ScrollBox>
              <LogoHeader />
              <VirtualMessageList />
            </ScrollBox>
            <StatusLine />
            <PromptInput />
          </FullscreenLayout>
        </AppStateProvider>
      </StatsProvider>
    </FpsMetricsProvider>
  </App>
</REPL>

启动时序

t=0ms    cli.tsx 开始
t=5ms    -- 路由判断
t=10ms   -- 动态 import main.tsx
t=25ms   main.tsx Commander.js 解析
t=30ms   -- 迁移系统执行
t=50ms   init() 开始
t=200ms  -- API 预连接
t=220ms  REPL.tsx 挂载
t=300ms  -- Ink 渲染引擎启动
t=350ms  -- 首帧渲染
t=400ms  -- 等待用户输入

从 0 到可交互,400ms。对一个 1500+ 文件的 TypeScript 项目来说,这个数字相当不错。

启动优化总结

技术效果
动态 import只加载当前模式需要的模块
memoize 初始化每个步骤只跑一次
API 预连接省掉首次请求的 TCP 握手
延迟遥测400KB OTEL 不阻塞启动
并行 MCP多个 MCP 服务器同时连接
渐进渲染先画框架,数据后填

这些技术单个都不新鲜,但组合在一起,效果立竿见影。