前回: 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 の実行時引数などから生成したオブジェクト
resolveConfigでResolvedConfig型の値を返す- inlineConfig の
configFileがfalseでなければloadConfigFromFileで読む (明示的に "使いません!!" としない限りロードする) - 次の順番でロードを試みる
configFileでファイル指定がある場合はそれをロードvite.config.jsvite.config.mjsvite.config.tsvite.config.cjs
- ESM の場合 (package.json の type が
module、または設定ファイルが.mjs・.ts)はbundleConfigFile- esbuildでビルド
- Pluginとして
externalize-depsとreplace-import-metaを適用している - そういうプラグインパッケージがあるわけじゃなく、ただ名前つけてるだけぽい
externalize-deps→ 外部パッケージをマークreplace-import-meta→import.meta.url,__dirname,__filenameを実体に置き換え
- inlineConfig の
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の配布を誰が責務を持つかの話- ドキュメント上では
ssrorhtmlが設定に渡せるようだが、実際はtruefalseもいける
- https/proxyの設定に応じて、
httporhttpsorhttp2モジュールでcreateServer(connectで作られたMiddlewareを渡す)
- https://ja.vitejs.dev/config/#server-middlewaremode
createWebSocketServer: HMR用のWebSocketサーバーの準備。基本的に↑で使っているサーバーをそのまま使う- Midlewareモードだったり
server.htr.serverの設定などによってはサーバーを作る - 先に作ったサーバーを利用する場合は、
upgrade要求で WebSocket コネクションを確立する
- Midlewareモードだったり
createPluginContainer
const container = await createPluginContainer(config, moduleGraph, watcher)
- そもそも
PluginContainerとは何なのかを知らないと読めなさそう - https://ja.vitejs.dev/guide/api-javascript.html#vitedevserver
指定したファイル上でプラグインフックを実行できる Rollup プラグインコンテナ。とある
- https://ja.vitejs.dev/guide/api-plugin.html#%E5%85%B1%E9%80%9A%E3%81%AE%E3%83%95%E3%83%83%E3%82%AF
開発中、Vite 開発サーバは、Rollup が行うのと同じ方法で Rollup ビルドフックを呼び出すプラグインコンテナを作成します。
- 適宜 Rollup ビルドを行うためのコンテナ
// 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でコードをparseresolve: ID(ファイルのURLやパス)を解決addWatchFile: chokiderの監視対象にファイルを乗せる
都度生成しているケースもあるし、TransformContext のように継承しているケースもある。
最終的に返される PluginContainer は次のメソッドを持つ
buildStart: すべてのプラグインを対象にbuildStartを呼ぶresolveId: ID(ファイルのURLやパス)を解決load: すべてのプラグインを対象に id を引数にloadを呼ぶtransform: すべてのプラグインを対象に id を引数にtransformを呼ぶclose: すべてのプラグインを対象にbuildEndとcloseBundleを呼ぶ
サーバー停止用ハンドラの生成
const closeHttpServer = createServerCloseFn(httpServer)
connectionのたびに全てをopenSocketsとして保持- 全ての接続に対して
destroyで破棄
ViteDevServer の生成
ここまで作ってきた全てを一通り保持する ViteDevServer オブジェクトを作る
詳細は次回以降。
感想
createServerだけでもやってることめっちゃ多かった- vite.config 書くときに「tsでも書けて便利〜〜」とか思ってたけど、裏で普通にesbuildしててそりゃそうだよなってなった
- RollUp用の
PluginContainerで複雑なことをやってんだなってのはわかった- 実際プラグイン書いてみないと真髄はわからなさそう
次回
createServer 読み終わってないので、続きをよむ