ランタイム向けの Environment API 
Release Candidate
Environment API は一般的にリリース候補段階にあります。エコシステムがそれらを実験し、構築できるように、メジャーリリース間での API の安定性を維持します。ただし、一部の特定の API はまだ実験的であることに注意してください。
ダウンストリームプロジェクトが新しい機能を実験し、それらを検証する時間を持った後、将来のメジャーリリースでこれらの新しい API を安定化する予定です(破壊的変更を含む可能性あり)。
リソース:
- 新しい API に関するフィードバックを収集する Feedback discussion
 - 新しい API が実装され、レビューされる Environment API PR
 
ぜひフィードバックをお寄せください。
環境ファクトリー 
環境ファクトリーは、エンドユーザーではなく、Cloudflare などの環境プロバイダーによって実装されることを目的としています。環境ファクトリーは、開発環境とビルド環境の両方でターゲットランタイムを使用する最も一般的なケースで、EnvironmentOptions を返します。デフォルトの環境オプションも設定できるため、ユーザーが設定する必要はありません。
function createWorkerdEnvironment(
  userConfig: EnvironmentOptions,
): EnvironmentOptions {
  return mergeConfig(
    {
      resolve: {
        conditions: [
          /*...*/
        ],
      },
      dev: {
        createEnvironment(name, config) {
          return createWorkerdDevEnvironment(name, config, {
            hot: true,
            transport: customHotChannel(),
          })
        },
      },
      build: {
        createEnvironment(name, config) {
          return createWorkerdBuildEnvironment(name, config)
        },
      },
    },
    userConfig,
  )
}設定ファイルは次のように記述できます:
import { createWorkerdEnvironment } from 'vite-environment-workerd'
export default {
  environments: {
    ssr: createWorkerdEnvironment({
      build: {
        outDir: '/dist/ssr',
      },
    }),
    rsc: createWorkerdEnvironment({
      build: {
        outDir: '/dist/rsc',
      },
    }),
  },
}フレームワークは次のコードを使用して、workerd ランタイム環境で SSR を実行できます:
const ssrEnvironment = server.environments.ssr新しい環境ファクトリーの作成 
Vite 開発サーバーは、デフォルトで client 環境と ssr 環境の 2 つの環境を公開します。クライアント環境はデフォルトではブラウザー環境であり、モジュールランナーは仮想モジュール /@vite/client をクライアントアプリにインポートすることによって実装されます。SSR 環境は、デフォルトでは Vite サーバーと同じ Node ランタイムで実行され、開発時は完全な HMR サポートによって、アプリケーションサーバーを使用してリクエストをレンダリングできます。
変換されたソースコードはモジュールと呼ばれ、各環境で処理されるモジュール間の関係はモジュールグラフに保持されます。これらのモジュールの変換されたコードは、実行される各環境に関連付けられたランタイムに送信されます。ランタイムでモジュールが評価されると、そのモジュールにインポートされたモジュールがリクエストされ、モジュールグラフのセクションの処理がトリガーされます。
Vite モジュールランナーは、最初に Vite プラグインで処理することで、任意のコードを実行できます。ランナーの実装がサーバーから分離されている点が server.ssrLoadModule とは異なります。これによりライブラリーおよびフレームワークの作者は、Vite サーバーとランナー間の通信レイヤーを実装できます。ブラウザーは、サーバーの WebSocket と HTTP リクエストを使用して、対応する環境と通信します。Node モジュールランナーは、同じプロセスで実行されているため、モジュールを処理するために関数呼び出しを直接実行できます。他の環境では、workerd などの JS ランタイムに接続するモジュール、または Vitest のようなワーカースレッドを実行するモジュールを実行できます。
この機能の目的の 1 つは、コードを処理および実行するためのカスタマイズ可能な API を提供することです。ユーザーは、公開されたプリミティブを使用して新しい環境ファクトリーを作成できます。
import { DevEnvironment, HotChannel } from 'vite'
function createWorkerdDevEnvironment(
  name: string,
  config: ResolvedConfig,
  context: DevEnvironmentContext
) {
  const connection = /* ... */
  const transport: HotChannel = {
    on: (listener) => { connection.on('message', listener) },
    send: (data) => connection.send(data),
  }
  const workerdDevEnvironment = new DevEnvironment(name, config, {
    options: {
      resolve: { conditions: ['custom'] },
      ...context.options,
    },
    hot: true,
    transport,
  })
  return workerdDevEnvironment
}DevEnvironment には 複数の通信レベル があります。フレームワークがランタイムに依存しないコードを簡単に記述できるように、可能な限り柔軟な通信レベルを実装することをお勧めします。
ModuleRunner 
モジュールランナーはターゲットランタイムでインスタンス化されます。次のセクションの全ての API は、特に断りのない限り vite/module-runner からインポートされます。このエクスポート・エントリーポイントは可能な限り軽量に保たれており、モジュールランナーを作成するために必要な最小限のものだけがエクスポートされます。
型シグネチャー:
export class ModuleRunner {
  constructor(
    public options: ModuleRunnerOptions,
    public evaluator: ModuleEvaluator = new ESModulesEvaluator(),
    private debug?: ModuleRunnerDebugger,
  ) {}
  /**
   * 実行する URL。
   * ルートからの相対的なファイルパス、サーバーパス、ID を受け付けます。
   */
  public async import<T = any>(url: string): Promise<T>
  /**
   * HMR リスナーを含むすべてのキャッシュをクリアします。
   */
  public clearCache(): void
  /**
   * 全キャッシュをクリア、全 HMR リスナーを削除、ソースマップサポートをリセットします。
   * このメソッドは HMR 接続を停止しません。
   */
  public async close(): Promise<void>
  /**
   * `close()` を呼び出してランナーを終了した場合は `true` を返します。
   */
  public isClosed(): boolean
}ModuleRunner のモジュール評価機能はコードの実行を担当します。Vite は ESModulesEvaluator をエクスポートしており、new AsyncFunction を使用してコードを評価します。JavaScript ランタイムが安全でない評価をサポートしていない場合は、独自の実装を提供できます。
モジュールランナーは import メソッドを公開します。Vite サーバーが full-reload HMR イベントをトリガーすると、影響を受けるすべてのモジュールが再実行されます。このとき、モジュールランナーは exports オブジェクトを更新しないことに注意してください(上書きされます)。最新の exports オブジェクトが必要であれば、 import を実行するか、もう一度 evaluatedModules からモジュールを取得する必要があります。
使用例:
import {
  ModuleRunner,
  ESModulesEvaluator,
  createNodeImportMeta,
} from 'vite/module-runner'
import { transport } from './rpc-implementation.js'
const moduleRunner = new ModuleRunner(
  {
    transport,
    createImportMeta: createNodeImportMeta, // モジュールランナーが Node.js で実行される場合
  },
  new ESModulesEvaluator(),
)
await moduleRunner.import('/src/entry-point.js')ModuleRunnerOptions 
interface ModuleRunnerOptions {
  /**
   * サーバーと通信するための一連のメソッド。
   */
  transport: ModuleRunnerTransport
  /**
   * ソースマップの解決方法を設定します。
   * `process.setSourceMapsEnabled` が使用可能な場合は `node` を優先します。
   * それ以外の場合は、デフォルトで `prepareStackTrace` を使用し、
   * `Error.prepareStackTrace` メソッドをオーバーライドします。
   * Vite によって処理されなかったファイルのファイル内容とソースマップの解決方法を設定する
   * オブジェクトを提供できます。
   */
  sourcemapInterceptor?:
    | false
    | 'node'
    | 'prepareStackTrace'
    | InterceptorOptions
  /**
   * HMR を無効にするか、HMR オプションを設定します。
   *
   * @default true
   */
  hmr?: boolean | ModuleRunnerHmr
  /**
   * カスタムモジュールキャッシュ。指定されていない場合は、モジュールランナーの
   * インスタンスごとに個別のモジュールキャッシュが作成されます。
   */
  evaluatedModules?: EvaluatedModules
}ModuleEvaluator 
型シグネチャー:
export interface ModuleEvaluator {
  /**
   * 変換後のコードに含まれるプレフィックスの行数。
   */
  startOffset?: number
  /**
   * Vite によって変換されたコードを評価します。
   * @param context 関数コンテキスト
   * @param code 変換されたコード
   * @param id モジュールを取得するために使用された ID 
   */
  runInlinedModule(
    context: ModuleRunnerContext,
    code: string,
    id: string,
  ): Promise<any>
  /**
   * 外部化されたモジュールを評価します。
   * @param file 外部モジュールへのファイル URL 
   */
  runExternalModule(file: string): Promise<any>
}Vite はデフォルトでこのインターフェイスを実装した ESModulesEvaluator をエクスポートします。コードの評価には new AsyncFunction を使用するので、インライン化されたソースマップがある場合は、新しい行が追加されたことを考慮して 2 行分のオフセットを追加する必要があります。これは ESModulesEvaluator によって自動的に実行されます。カスタムの Evaluator は行を追加しません。
ModuleRunnerTransport 
型シグネチャー:
interface ModuleRunnerTransport {
  connect?(handlers: ModuleRunnerTransportHandlers): Promise<void> | void
  disconnect?(): Promise<void> | void
  send?(data: HotPayload): Promise<void> | void
  invoke?(data: HotPayload): Promise<{ result: any } | { error: any }>
  timeout?: number
}RPC 経由または関数を直接呼び出して環境と通信するトランスポートオブジェクト。invoke メソッドが実装されていない場合、send メソッドと connect メソッドの実装が必須となります。Vite は内部で invoke を構築します。
次の例のように、モジュールランナーがワーカー スレッドで作成されるサーバー上の HotChannel インスタンスと結合する必要があります:
import { parentPort } from 'node:worker_threads'
import { fileURLToPath } from 'node:url'
import {
  ESModulesEvaluator,
  ModuleRunner,
  createNodeImportMeta,
} from 'vite/module-runner'
/** @type {import('vite/module-runner').ModuleRunnerTransport} */
const transport = {
  connect({ onMessage, onDisconnection }) {
    parentPort.on('message', onMessage)
    parentPort.on('close', onDisconnection)
  },
  send(data) {
    parentPort.postMessage(data)
  },
}
const runner = new ModuleRunner(
  {
    transport,
    createImportMeta: createNodeImportMeta,
  },
  new ESModulesEvaluator(),
)import { BroadcastChannel } from 'node:worker_threads'
import { createServer, RemoteEnvironmentTransport, DevEnvironment } from 'vite'
function createWorkerEnvironment(name, config, context) {
  const worker = new Worker('./worker.js')
  const handlerToWorkerListener = new WeakMap()
  const client = {
    send(payload: HotPayload) {
      worker.postMessage(payload)
    },
  }
  const workerHotChannel = {
    send: (data) => worker.postMessage(data),
    on: (event, handler) => {
      // クライアントはすでに接続されています
      if (event === 'vite:client:connect') return
      if (event === 'vite:client:disconnect') {
        const listener = () => {
          handler(undefined, client)
        }
        handlerToWorkerListener.set(handler, listener)
        worker.on('exit', listener)
        return
      }
      const listener = (value) => {
        if (value.type === 'custom' && value.event === event) {
          handler(value.data, client)
        }
      }
      handlerToWorkerListener.set(handler, listener)
      worker.on('message', listener)
    },
    off: (event, handler) => {
      if (event === 'vite:client:connect') return
      if (event === 'vite:client:disconnect') {
        const listener = handlerToWorkerListener.get(handler)
        if (listener) {
          worker.off('exit', listener)
          handlerToWorkerListener.delete(handler)
        }
        return
      }
      const listener = handlerToWorkerListener.get(handler)
      if (listener) {
        worker.off('message', listener)
        handlerToWorkerListener.delete(handler)
      }
    },
  }
  return new DevEnvironment(name, config, {
    transport: workerHotChannel,
  })
}
await createServer({
  environments: {
    worker: {
      dev: {
        createEnvironment: createWorkerEnvironment,
      },
    },
  },
})on / off メソッドが存在する場合は、それらのメソッドで vite:client:connect / vite:client:disconnect イベントを実装してください。vite:client:connect イベントは接続が確立されたときに発生し、vite:client:disconnect イベントは接続が閉じられたときに発生する必要があります。イベントハンドラーに渡される HotChannelClient オブジェクトは、同じ接続に対して同じ参照を持つ必要があります。
HTTP リクエストを使用してランナーとサーバー間で通信する別の例:
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
export const runner = new ModuleRunner(
  {
    transport: {
      async invoke(data) {
        const response = await fetch(`http://my-vite-server/invoke`, {
          method: 'POST',
          body: JSON.stringify(data),
        })
        return response.json()
      },
    },
    hmr: false, // HMR は transport.connect を必要とするため HMR を無効にする
  },
  new ESModulesEvaluator(),
)
await runner.import('/entry.js')この場合、NormalizedHotChannel の handleInvoke メソッドを使用できます:
const customEnvironment = new DevEnvironment(name, config, context)
server.onRequest((request: Request) => {
  const url = new URL(request.url)
  if (url.pathname === '/invoke') {
    const payload = (await request.json()) as HotPayload
    const result = customEnvironment.hot.handleInvoke(payload)
    return new Response(JSON.stringify(result))
  }
  return Response.error()
})ただし、HMR をサポートするためには send メソッドと connect メソッドが必要です。send メソッドは通常、カスタムイベントがトリガーされたときに呼び出されます(import.meta.hot.send("my-event") のように)。
Vite は SSR 中の HMR をサポートするために、メインエントリーポイントから createServerHotChannel をエクスポートします。