Viteのコードを読む - createServer(ViteDevServer生成まで)

前回: Viteのコードを読む - CLI経由でのコア実行の流れ - memo_md

viteパッケージ内部 / サーバー起動部分をよむ

vite dev vite serve などで起動する packages/vite/src/node/server をよむ

ファイル一覧

.
├── __tests__
├── hmr.ts
├── index.ts
├── middlewares
│   ├── base.ts
│   ├── error.ts
│   ├── indexHtml.ts
│   ├── proxy.ts
│   ├── spaFallback.ts
│   ├── static.ts
│   ├── time.ts
│   └── transform.ts
├── moduleGraph.ts
├── openBrowser.ts
├── pluginContainer.ts
├── searchRoot.ts
├── send.ts
├── sourcemap.ts
├── transformRequest.ts
└── ws.ts

CLI からの起動は ↓

const { createServer } = await import('./server')
try {
  const server = await createServer({
    root,
    base: options.base,
    mode: options.mode,
    configFile: options.config,
    logLevel: options.logLevel,
    clearScreen: options.clearScreen,
    server: cleanOptions(options)
  })

  ...

vite/packages/vite/src/node/server/index.ts # createServer が該当

createServer

コンフィグの読み込みとサーバー生成

const config = await resolveConfig(inlineConfig, 'serve', 'development')
  • inlineConfig は CLI の実行時引数などから生成したオブジェクト
  • resolveConfigResolvedConfig 型の値を返す
    • inlineConfig の configFilefalse でなければ loadConfigFromFile で読む (明示的に "使いません!!" としない限りロードする)
    • 次の順番でロードを試みる
      1. configFile でファイル指定がある場合はそれをロード
      2. vite.config.js
      3. vite.config.mjs
      4. vite.config.ts
      5. vite.config.cjs
    • ESM の場合 (package.json の type が module、または設定ファイルが .mjs.ts )は bundleConfigFile
      • esbuildでビルド
      • Pluginとして externalize-depsreplace-import-meta を適用している
      • そういうプラグインパッケージがあるわけじゃなく、ただ名前つけてるだけぽい
        • externalize-deps → 外部パッケージをマーク
        • replace-import-metaimport.meta.url, __dirname, __filename を実体に置き換え
  const httpsOptions = await resolveHttpsConfig(
    config.server.https,
    config.cacheDir
  )
  • vite/packages/vite/src/node/http.ts # resolveHttpsConfig
  • https接続用の ca,cert,key,pfx ファイルをロード
const middlewares = connect() as Connect.Server
const httpServer = middlewareMode
  ? null
  : await resolveHttpServer(serverConfig, middlewares, httpsOptions)
const ws = createWebSocketServer(httpServer, config, httpsOptions)
  • connect でサーバのMiddlewareを作成
  • server設定で middlewareMode が設定されていなければ resolveHttpServer
    • https://ja.vitejs.dev/config/#server-middlewaremode
      • index.html の配布を誰が責務を持つかの話
      • ドキュメント上では ssr or html が設定に渡せるようだが、実際は true false もいける
    • https/proxyの設定に応じて、 http or https or http2 モジュールで createServer (connectで作られたMiddlewareを渡す)
  • createWebSocketServer : HMR用のWebSocketサーバーの準備。基本的に↑で使っているサーバーをそのまま使う
    • Midlewareモードだったりserver.htr.server の設定などによってはサーバーを作る
    • 先に作ったサーバーを利用する場合は、upgrade 要求で WebSocket コネクションを確立する

createPluginContainer

  const container = await createPluginContainer(config, moduleGraph, watcher)
// we should create a new context for each async hook pipeline so that the
// active plugin in that pipeline can be tracked in a concurrency-safe manner.
// using a class to make creating new contexts more efficient
class Context implements PluginContext {
  ...
}

次のようなものを持つ(特徴的ぽい一部だけ抜粋)

  • parse : acornでコードをparse
  • resolve : ID(ファイルのURLやパス)を解決
  • addWatchFile : chokiderの監視対象にファイルを乗せる

都度生成しているケースもあるし、TransformContext のように継承しているケースもある。

最終的に返される PluginContainer は次のメソッドを持つ

  • buildStart : すべてのプラグインを対象に buildStart を呼ぶ
  • resolveId : ID(ファイルのURLやパス)を解決
  • load : すべてのプラグインを対象に id を引数に load を呼ぶ
  • transform : すべてのプラグインを対象に id を引数に transform を呼ぶ
  • close : すべてのプラグインを対象に buildEndcloseBundle を呼ぶ

サーバー停止用ハンドラの生成

const closeHttpServer = createServerCloseFn(httpServer)

ViteDevServer の生成

ここまで作ってきた全てを一通り保持する ViteDevServer オブジェクトを作る 詳細は次回以降。


感想

  • createServer だけでもやってることめっちゃ多かった
  • vite.config 書くときに「tsでも書けて便利〜〜」とか思ってたけど、裏で普通にesbuildしててそりゃそうだよなってなった
  • RollUp用の PluginContainer で複雑なことをやってんだなってのはわかった

次回

createServer 読み終わってないので、続きをよむ