RunnableDevEnvironment runner import method
RunnableDevEnvironment has a runner property of type ModuleRunner. Call runner.import(url) to fetch, transform, and evaluate a module from the Vite module graph. The url parameter accepts a file path, server path, or id relative to the root. The method returns an instantiated module with full HMR support. It is the modern replacement for server.ssrLoadModule.
RunnableDevEnvironment communication mechanism
RunnableDevEnvironment evaluates modules in the same runtime as the Vite server, so values cross the boundary in-process instead of being serialized. This is what distinguishes it from FetchableDevEnvironment, which can only communicate through serialized Request/Response objects over the Fetch API. Using a RunnableDevEnvironment requires the runner's runtime to be the same as the one the Vite server is running in.
isRunnableDevEnvironment function
Use the isRunnableDevEnvironment function to guard access to the runner property of an environment, since not all environments are RunnableDevEnvironment instances.
ModuleRunner lazy evaluation and source maps
The runner is evaluated lazily only when it is accessed for the first time. Vite enables source map support when the runner is created by calling process.setSourceMapsEnabled or by overriding Error.prepareStackTrace if it is not available.
ModuleRunner class type signature
ModuleRunner constructor takes ModuleRunnerOptions, ModuleEvaluator (defaults to ESModulesEvaluator), and optional ModuleRunnerDebugger. The import<T>(url: string) method accepts file path, server path, or id relative to root and returns Promise<T>. The clearCache() method clears all caches including HMR listeners. The close() method clears all caches, removes HMR listeners, resets sourcemap support but doesn't stop HMR connection and returns Promise<void>. The isClosed() method returns boolean indicating if close() has been called.
ESModulesEvaluator and module evaluation
Vite exports ESModulesEvaluator out of the box which uses new AsyncFunction to evaluate code. You can provide your own ModuleEvaluator implementation if your JavaScript runtime doesn't support unsafe evaluation. When Vite server triggers full-reload HMR event, all affected modules will be re-executed. Module Runner doesn't update exports object when this happens (it overrides it), so you need to run import or get the module from evaluatedModules again if you rely on having the latest exports object.
ModuleRunnerOptions interface
ModuleRunnerOptions has the following properties: transport (ModuleRunnerTransport, required) - a set of methods to communicate with the server; sourcemapInterceptor (false | 'node' | 'prepareStackTrace' | InterceptorOptions, optional) - configures how source maps are resolved, prefers 'node' if process.setSourceMapsEnabled is available, otherwise defaults to 'prepareStackTrace'; hmr (boolean | ModuleRunnerHmr, defaults to true) - disables HMR or configures HMR options; evaluatedModules (EvaluatedModules, optional) - custom module cache, creates separate cache per instance if not provided.
ModuleEvaluator interface
ModuleEvaluator interface has startOffset property (number, optional) - number of prefixed lines in transformed code. The runInlinedModule(context: ModuleRunnerContext, code: string, id: string) method evaluates code transformed by Vite and returns Promise<any>. The runExternalModule(file: string) method evaluates externalized module from File URL and returns Promise<any>.
ESModulesEvaluator source map offset
ESModulesEvaluator uses new AsyncFunction to evaluate code, so if code has inlined source map it should contain an offset of 2 lines to accommodate for new lines added by AsyncFunction constructor. This offset is added automatically by ESModulesEvaluator. Custom evaluators will not add additional lines automatically.
ModuleRunnerTransport interface
ModuleRunnerTransport interface has the following methods: connect(handlers: ModuleRunnerTransportHandlers) optional - returns Promise<void> or void; disconnect() optional - returns Promise<void> or void; send(data: HotPayload) optional - returns Promise<void> or void; invoke(data: HotPayload) optional - returns Promise<{result: any} | {error: any}>. It also has timeout property optional (number). When invoke method is not implemented, send and connect methods are required. Vite will construct invoke internally.
ModuleRunner example with Node.js
Example of using ModuleRunner in Node.js: import { ModuleRunner, ESModulesEvaluator, createNodeImportMeta } from 'vite/module-runner'; import { transport } from './rpc-implementation.js'; const moduleRunner = new ModuleRunner({ transport, createImportMeta: createNodeImportMeta }, new ESModulesEvaluator()); await moduleRunner.import('/src/entry-point.js');
ModuleRunnerTransport with HTTP invoke
Example of ModuleRunnerTransport using HTTP for invoke without HMR: 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 }, new ESModulesEvaluator()); await runner.import('/entry.js');
Worker thread ModuleRunner transport implementation
Example of ModuleRunnerTransport in a worker thread: import { parentPort } from 'node:worker_threads'; import { ESModulesEvaluator, ModuleRunner, createNodeImportMeta } from 'vite/module-runner'; 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());