Open18

Vitestを読む

nus3nus3

動作確認

rootでpnpm run devしつつ、pnpm run testでコアなテストケースは開発しながら確認できる。

また、任意のexamplesフォルダに移動し、pnpm run testを実行すればexamlesのテストも確認できる。

nus3nus3

デバッグ

VSCodeの場合、ブレークポイントを設定しRun and DebugからJavaScript Debug Terminalを選択、そのターミナル上でpnpm run testを実行するとブレークポイントに設定した部分で止まる。また1ステップずつ処理を確認できる

デバッグ中に変数の中身がVSCode上で確認できる。便利。
デバッグ中に変数の中身がVSCode上で確認できる

nus3nus3

packages

cd test/core
pnpm run test test/basic.test.ts

test/coreのbasic.test.tsを実行しつつ、packageが読み込まれる順に実装を眺める

  1. vitest
nus3nus3
nus3nus3

手元で実行してみるとimport('../cli')してる
https://github.com/vitest-dev/vitest/blob/6157282cc04494afc85085775d1f565c0d0dbb5a/packages/vitest/src/node/cli/cli-wrapper.ts#L64-L67

'../cli'ではcreateCLI().parse()を実行してる
https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/cli.ts

createCLI()
https://github.com/vitest-dev/vitest/blob/6157282cc04494afc85085775d1f565c0d0dbb5a/packages/vitest/src/node/cli/cac.ts#L61-L179

createCLI()ではcacというパッケージを利用している
cacはCommand And Conquerの略で、CLIアプリを作るためのライブラリぽい
https://github.com/cacjs/cac

nus3nus3

Vitestの場合、シンプルにテストを実行するならvitest runコマンドを実行する
https://vitest.dev/guide/cli.html#vitest-run

このvitest runではrunが使われてそう
https://github.com/vitest-dev/vitest/blob/6157282cc04494afc85085775d1f565c0d0dbb5a/packages/vitest/src/node/cli/cac.ts#L147-L149

runの実装は以下で、start関数が呼ばれてる
引数であるcliFiltersには指定したテストファイルのパスが配列で格納されていた
['test/basic.test.ts']
https://github.com/vitest-dev/vitest/blob/6157282cc04494afc85085775d1f565c0d0dbb5a/packages/vitest/src/node/cli/cac.ts#L219-L222

start関数も同ファイルで定義されている
https://github.com/vitest-dev/vitest/blob/6157282cc04494afc85085775d1f565c0d0dbb5a/packages/vitest/src/node/cli/cac.ts#L238-L256

start関数の中ではstartVitest()が実行されている

nus3nus3

startVitest()の実装は以下

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/cli/cli-api.ts#L31-L108

まずcreateVitest()で context を作ってそう

createVitest()の実装は以下
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/create.ts#L12-L38

Vitest クラス
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/core.ts#L36-L970

VitestPackageInstaller クラス
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/packageInstaller.ts#L10-L52

なんかサーバー作ってるけど、listen はしてない
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/create.ts#L32-L35

ctx.start(cliFilters)の実装は以下
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/core.ts#L387-L424

git からファイルの変更差分を検知してそう
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/core.ts#L452-L463

テストの実行はここら辺かな?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/core.ts#L419

テストを実行してそうthis.pool.runTests(paths, invalidates)
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/core.ts#L539

this.pool.runTests(paths, invalidates)の実装は以下
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pool.ts#L78

filesByPool.threadsの中にテストファイルのパスが格納されている

thread はcreateThreadsPoolで作られる。createThreadsPoolの実装は以下
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L40-L214

pools[pool]!.runTests(specs, invalidate)が実行され
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pool.ts#L158

pools[pool]!.runTests(specs, invalidate)は以下の部分が実行される?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L128

実態はここかな?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L91-L126

await pool.run(data, { transferList: [workerPort], name })
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L108

この pool は Tinypool
https://github.com/tinylibs/tinypool

Node.js Worker Thread Pool をミニマムで作ったもの。pool を作った際にワーカーの内容を定義している。
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L86

worker の指定は以下でやっており、filename の値は'/Home/vitest/packages/vitest/dist/worker.js'
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L58

worker の実装はここ
https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/workers.ts

nus3nus3

worker(threads?) の中で何が実装されているかを定義しているのは
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L40-L214

この中で Tinypool にはpackages/vitest/src/workers.tsが渡されてる
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L58


以下 Claude3

Worker はプロセス間の並列処理、Threads はプロセス内の並列処理を行うためのモデル
プロセス間で並列処理をすることで同じメモリ領域を参照しない、しかし、プロセス間通信のオーバーヘッドは発生する。
逆に threads はスレッド間でメッセージングを使って直接通信できるが、メモリが共有されるため、データの競合が発生する可能性がある。

CPU バウンドと I/O バウンド
https://yohei-a.hatenablog.jp/entry/20120205/1328432481

ディスクとの入出力は行わないが、計算が重いプログラムを CPU バウンド
ディスクとの入出力が多いプログラムを I/O バウンド

Worker Pool を使うことで複数のスレッドを効率的に並列実行できる

  1. 指定した数のスレッドをあらかじめ作成する。
  2. 実行したいタスクをキューに追加する。
  3. スレッドが空いている場合、キューからタスクを取り出して実行する。
  4. タスクが完了するとスレッドはプールに戻る。
  5. pool のスレッド数は動的に増減できる

Tinypool を作成するときもスレッド数を指定しており、ローカルで実行した場合、この数はどちらも 6 だった
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L63-L64

TODO: Node.js の worker と threads を実装してみる

workers/threads.js
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L55
これはsrc/runtime/workers/threads.tsに実装がある
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/rollup.config.js#L39

ThreadsBaseWorkerが export されている
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/workers/threads.ts

この worker も data に詰め込まれた状態で pool.run が実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/node/pools/threads.ts#L95-L108

pool.run が実行されると
packages/vitest/src/runtime/worker.ts のrunが実行され、この中のworker.runTests(state)が実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/worker.ts#L65

worker.runTests(state)はの実装は以下でrunBaseTestsが実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/workers/threads.ts#L12-L14

runBaseTestsではimport('../runBaseTests'),で相対パスの位置にあるpackages/vitest/src/runtime/runBaseTests.tsが import され、その中のrunが実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/workers/base.ts#L21-L47

runBaseTestsの中でrunの中のawait startTests([file], runner)部分が実際にテストを実行していそう
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/runBaseTests.ts#L33-L56

nus3nus3

@vitest/runner

nus3nus3

テストを実行していそうなstartTests@vitest/runnerで定義されている
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L391-L406

VitestRunner で公開されている API はドキュメントに乗ってる
https://vitest.dev/advanced/runner

startTestsrunFilesrunSuite
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L278-L366

runSuiterunSuiteChildrunTest
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L368-L374

runSuiteChildrunTest
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L117-L249

runTestの中ではgetFnでテスト対象の fn ?を取得して実行している
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L155-L158

getFnは WeakMap に格納されている
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/map.ts


以下 Claude3

WeakMap を使うことで Map とは異なり、キーとなるオブジェクトが参照されなくなると GC によりメモリが解放される
WeakMap を使うことでメモリりーすが低くなる。今回の Vitest の Test や Suite のように一時的に使われるものを WeakMap で格納することでメモリリークを防ぎつつオブジェクトの生存期間を気にしないで良くなる


getFnの返り値

fn.toString()
'(...args) => {
    return Promise.race([fn(...args), new Promise((resolve, reject) => {
      var _a;
      const timer = setTimeout(() => {
        clearTimeout(timer);
        reject(new Error(makeTimeoutMsg(isHook, timeout)));
      }, timeout);
      (_a = timer.unref) == null ? void 0 : _a.call(timer);
    })]);
  }'

これはwithTimeoutの実装
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/context.ts#L23-L43

sefFnしているのはcreateSuiteCollectorの中
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/suite.ts#L107-L263
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/suite.ts#L144-L147

setFnするタイミングはデバッグの call stack を見ると

  1. packages/vitest/src/runtime/runBaseTests.ts のrun()時のstartTests()
  2. packages/runner/src/collect.ts のcollectTests()
  3. runner.importFile
  4. VitestTestRunner.importFile
  5. ViteNodeRunner.executeId
  6. ViteNodeRunner.cachedRequest
  7. ViteNodeRunner.directRequest
  8. ViteNodeRunner.runModule

テストコードで使うsuitecreateSuiteCollector を実行してる。
createSuiteCollector、createSuite、suite
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/suite.ts#L12C8-L21

このcreateSuiteCollectorの中でsetFnしており、withTimeoutの第一引数に実行されるテスト関数が渡されている
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/suite.ts#L144-L148

第一引数はwithFixtures(handler, context)
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/fixture.ts#L66-L113

手元で実行してみると以下の部分のように handler(fn)を実行している
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/fixture.ts#L74-L75

この handler(fn) は以下のようになっている

handler.toString()
'async () => {
  __vite_ssr_import_0__.assert.equal(Math.sqrt(4), __vite_ssr_import_1__.two);
  __vite_ssr_import_0__.assert.equal(Math.sqrt(2), Math.SQRT2);
  __vite_ssr_import_0__.expect(Math.sqrt(144)).toStrictEqual(12);
}'

ここまでくるとテストが単体で実行できるようになっていそう

このhandlercreateSuiteCollectorの中で定義されている
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/suite.ts#L129

TODO: handlerを定義している方法を調べるところから

nus3nus3

テスト実行時(startTests)に、対象のテストを取得するcollectTests
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/run.ts#L394

collectTestsの実装箇所
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/collect.ts#L13-L81

pathe
あらゆる環境で使えるファイルパスモジュールで UnJS の一つ
https://github.com/unjs/pathe

UnJS は、Nuxt 開発チームが中心となって開発・メンテナンスされている、あらゆる JavaScript フレームワーク上で統一的に動作するユーティリティーツール・ライブラリ群

https://zenn.dev/ytr0903/articles/c6c42147ed29be
https://zenn.dev/ytr0903/articles/6b50bf790c340b

対象のテストファイルをrunner.importFile(filepath, 'collect')してる
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/runner/src/collect.ts#L40

importFileではthis.__vitest_executor.executeId(filepath)が実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/runners/test.ts#L20-L24

this.__vitest_executor.executeId(filepath)は手元ではViteNodeRunnerexecuteIdが実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L176-L179

Runner の定義はここ
TODO: どのように Runner を定義しているのか
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/runBaseTests.ts#L23

executeIdではcachedRequestを実行

循環参照を防ぐ処理とかしてそう
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L193-L197

directRequestが実行される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L208

directRequestの定義場所
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L264-L409

directRequestの中で対象のテストファイルパスに対して、await this.options.fetchModule(id)を実行してる
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L282

this.options.fetchModule()の実装場所
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/execute.ts#L91-L108

this.options.fetchModule()の返り値のcodeの値

const __vite_ssr_import_0__ = await __vite_ssr_import__(
  "/@fs/Users/nus3/dev/fork/vitest/packages/vitest/dist/index.js",
  { importedNames: ["assert", "expect", "it", "suite", "test"] }
);
const __vite_ssr_import_1__ = await __vite_ssr_import__("/src/submodule.ts", {
  importedNames: ["two"],
});
const __vite_ssr_import_2__ = await __vite_ssr_import__("/src/timeout.ts", {
  importedNames: ["timeout"],
});

__vite_ssr_import_0__.test("Math.sqrt()", async () => {
  __vite_ssr_import_0__.assert.equal(Math.sqrt(4), __vite_ssr_import_1__.two);
  __vite_ssr_import_0__.assert.equal(Math.sqrt(2), Math.SQRT2);
  __vite_ssr_import_0__.expect(Math.sqrt(144)).toStrictEqual(12);
});

TODO: vm.runInContextを使ってみる

テスト対象のファイルやテストで使われるモジュールを実行できる形にトランスパイルしてそう

this.options.fetchModule()がトランスパイルしてそうなので中身をもっとみる
this.options.fetchModule()は何回か呼ばれてそう

サブモジュールをthis.options.fetchModule()した時の返り値

const two = 1 + 1;
Object.defineProperty(__vite_ssr_exports__, "two", {
  enumerable: true,
  configurable: true,
  get() {
    return two;
  },
});

rpc().fetch(id, getTransformMode())の返り値にはトランスパイルされた後のコードが生成されてる?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/execute.ts#L107

rpc()は何なのか
rpc は Proxy オブジェクト
rpc 周りの実装はここにありそう
vitest/packages/vitest/src/runtime/rpc.ts


以下 Claude3

rpc は、Remote Procedure Call(リモートプロシージャコール)の略です。
これは、プログラム間でデータを交換したり、他のプログラムの機能を利用したりするための技術の一種です。
Vitest において rpc は Worker と Runner の間での通信に使われています。

Worker: テストを実行する役割を持つプロセス
Runner: Worker の管理やテスト実行の調整をする親プロセス

rpc を使うことで、Runner から Worker にテストの実行を指示したり、Worker から Runner にテスト結果を送ったりと、プロセス間でデータのやり取りができます。
具体的には、createRuntimeRpc という関数で rpc のインスタンスを作成し、それを介して Worker と Runner が通信を行なっているようです。rpc の実装には birpc というライブラリが使われているみたいですね。
rpc はクライアント・サーバー形式の分散システムでよく使われる技術概念ですが、Vitest ではプロセスレベルでのメッセージングにも活用されているということになります。


birpcというライブラリが使われている
https://github.com/antfu/birpc

このrpcを使ってる箇所でコメントが記載されており
Claude さんが言ってるように、ワーカー(Worker、threads、子プロセス)とメインスレッド間の通信に使用される
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vitest/src/runtime/worker.ts#L38-L39

が、トランスパイルが実施されるタイミングがわからぬ...

トランスパイル後のコードで使われている__vite_ssr_import__を全文検索すると以下の部分でのみ使われていた
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L386

TODO: packages/vite-node/src/client.ts の部分を読めば、どこでトランスパイルしてるのかわかるかも

nus3nus3

vite-node

Node.jsでのテスト実行時にトランスパイルをやってそうなパッケージ

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L386

nus3nus3

Vite でサーバー作ってその中で fetchModule を定義している?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/server.ts#L140-L156

vite-nodeでは server と client があって、server 側でトランスパイル、client 側で対象ファイルを実行してるんか?
https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/README.md

トランスパイルをしてそうな箇所

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/server.ts#L260

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/server.ts#L304

ViteDevServer の transformRequest を使っている

/**
   * プログラムで URL を解決、読込、変換して、HTTP リクエストパイプラインを
   * 経由せずに結果を取得します。
   */
  transformRequest(
    url: string,
    options?: TransformOptions,
  ): Promise<TransformResult | null>

https://ja.vitejs.dev/guide/api-javascript.html

手元で確かめたトランスパイル後のコード

const __vite_ssr_import_0__ = await __vite_ssr_import__(
  "/@fs/Users/nus3/dev/fork/vitest/packages/vitest/dist/index.js",
  { importedNames: ["assert", "expect", "it", "suite", "test"] }
);
const __vite_ssr_import_1__ = await __vite_ssr_import__("/src/submodule.ts", {
  importedNames: ["two"],
});
const __vite_ssr_import_2__ = await __vite_ssr_import__("/src/timeout.ts", {
  importedNames: ["timeout"],
});

__vite_ssr_import_0__.test("Math.sqrt()", async () => {
  __vite_ssr_import_0__.assert.equal(Math.sqrt(4), __vite_ssr_import_1__.two);
  __vite_ssr_import_0__.assert.equal(Math.sqrt(2), Math.SQRT2);
  __vite_ssr_import_0__.expect(Math.sqrt(144)).toStrictEqual(12);
});

/src/submodule.ts

const two = 1 + 1;
Object.defineProperty(__vite_ssr_exports__, "two", {
  enumerable: true,
  configurable: true,
  get() {
    return two;
  },
});
s;

サブモジュールでは__vite_ssr_exports__が使われていて、トップレベルのモジュールでは__vite_ssr_import__を使ってモジュールを import している。

ViteNodeRunner 側では context に import や export のために必要なものを格納してから ViteNodeServer 側でトランスパイルされた関数を vm で実行している

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L384-L398

https://github.com/vitest-dev/vitest/blob/df6a432856b1165d1e7c4129ee9f35cc4fa6a365/packages/vite-node/src/client.ts#L425-L426