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 の実行時引数などから生成したオブジェクト
resolveConfigでResolvedConfig型の値を返す
inlineConfig の configFile が false でなければ loadConfigFromFile で読む (明示的に "使いません!!" としない限りロードする)
- 次の順番でロードを試みる
configFile でファイル指定がある場合はそれをロード
-
vite.config.js -
vite.config.mjs -
vite.config.ts -
vite.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を実体に置き換え
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の設定に応じて、 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)
- そもそも
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として保持
https://nodejs.org/api/http.html#event-connection
全ての接続に対して destroy で破棄
ViteDevServer の生成
ここまで作ってきた全てを一通り保持する ViteDevServer オブジェクトを作る
詳細は次回以降。
感想
createServerだけでもやってることめっちゃ多かった- vite.config 書くときに「tsでも書けて便利〜〜」とか思ってたけど、裏で普通にesbuildしててそりゃそうだよなってなった
- RollUp用の
PluginContainerで複雑なことをやってんだなってのはわかった
実際プラグイン書いてみないと真髄はわからなさそう
次回
createServer 読み終わってないので、続きをよむ