Skip to content

Using Environment Instances

Release Candidate

The Environment API is generally in the release candidate phase. We'll maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. However, note that some specific APIs are still considered experimental.

We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them.

Resources:

Please share your feedback with us.

Accessing the Environments

During dev, the available environments in a dev server can be accessed using server.environments:

js
// create the server, or get it from the configureServer hook const server = await createServer(/* options */)  const clientEnvironment = server.environments.client clientEnvironment.transformRequest(url) console.log(server.environments.ssr.moduleGraph)

You can also access the current environment from plugins. See the Environment API for Plugins for more details.

DevEnvironment class

During dev, each environment is an instance of the DevEnvironment class:

ts
class DevEnvironment {  /**  * Unique identifier for the environment in a Vite server.  * By default Vite exposes 'client' and 'ssr' environments.  */  name: string  /**  * Communication channel to send and receive messages from the  * associated module runner in the target runtime.  */  hot: NormalizedHotChannel  /**  * Graph of module nodes, with the imported relationship between  * processed modules and the cached result of the processed code.  */  moduleGraph: EnvironmentModuleGraph  /**  * Resolved plugins for this environment, including the ones  * created using the per-environment `create` hook  */  plugins: Plugin[]  /**  * Allows to resolve, load, and transform code through the  * environment plugins pipeline  */  pluginContainer: EnvironmentPluginContainer  /**  * Resolved config options for this environment. Options at the server  * global scope are taken as defaults for all environments, and can  * be overridden (resolve conditions, external, optimizedDeps)  */  config: ResolvedConfig & ResolvedDevEnvironmentOptions   constructor(  name: string,  config: ResolvedConfig,  context: DevEnvironmentContext,  )   /**  * Resolve the URL to an id, load it, and process the code using the  * plugins pipeline. The module graph is also updated.  */  async transformRequest(url: string): Promise<TransformResult | null>   /**  * Register a request to be processed with low priority. This is useful  * to avoid waterfalls. The Vite server has information about the  * imported modules by other requests, so it can warmup the module graph  * so the modules are already processed when they are requested.  */  async warmupRequest(url: string): Promise<void> }

With DevEnvironmentContext being:

ts
interface DevEnvironmentContext {  hot: boolean  transport?: HotChannel | WebSocketServer  options?: EnvironmentOptions  remoteRunner?: {  inlineSourceMap?: boolean  }  depsOptimizer?: DepsOptimizer }

and with TransformResult being:

ts
interface TransformResult {  code: string  map: SourceMap | { mappings: '' } | null  etag?: string  deps?: string[]  dynamicDeps?: string[] }

An environment instance in the Vite server lets you process a URL using the environment.transformRequest(url) method. This function will use the plugin pipeline to resolve the url to a module id, load it (reading the file from the file system or through a plugin that implements a virtual module), and then transform the code. While transforming the module, imports and other metadata will be recorded in the environment module graph by creating or updating the corresponding module node. When processing is done, the transform result is also stored in the module.

transformRequest naming

We are using transformRequest(url) and warmupRequest(url) in the current version of this proposal so it is easier to discuss and understand for users used to Vite's current API. Before releasing, we can take the opportunity to review these names too. For example, it could be named environment.processModule(url) or environment.loadModule(url) taking a page from Rollup's context.load(id) in plugin hooks. For the moment, we think keeping the current names and delaying this discussion is better.

Separate Module Graphs

Each environment has an isolated module graph. All module graphs have the same signature, so generic algorithms can be implemented to crawl or query the graph without depending on the environment. hotUpdate is a good example. When a file is modified, the module graph of each environment will be used to discover the affected modules and perform HMR for each environment independently.

INFO

Vite v5 had a mixed Client and SSR module graph. Given an unprocessed or invalidated node, it isn't possible to know if it corresponds to the Client, SSR, or both environments. Module nodes have some properties prefixed, like clientImportedModules and ssrImportedModules (and importedModules that returns the union of both). importers contains all importers from both the Client and SSR environment for each module node. A module node also has transformResult and ssrTransformResult. A backward compatibility layer allows the ecosystem to migrate from the deprecated server.moduleGraph.

Each module is represented by a EnvironmentModuleNode instance. Modules may be registered in the graph without yet being processed (transformResult would be null in that case). importers and importedModules are also updated after the module is processed.

ts
class EnvironmentModuleNode {  environment: string   url: string  id: string | null = null  file: string | null = null   type: 'js' | 'css'   importers = new Set<EnvironmentModuleNode>()  importedModules = new Set<EnvironmentModuleNode>()  importedBindings: Map<string, Set<string>> | null = null   info?: ModuleInfo  meta?: Record<string, any>  transformResult: TransformResult | null = null   acceptedHmrDeps = new Set<EnvironmentModuleNode>()  acceptedHmrExports: Set<string> | null = null  isSelfAccepting?: boolean  lastHMRTimestamp = 0  lastInvalidationTimestamp = 0 }

environment.moduleGraph is an instance of EnvironmentModuleGraph:

ts
export class EnvironmentModuleGraph {  environment: string   urlToModuleMap = new Map<string, EnvironmentModuleNode>()  idToModuleMap = new Map<string, EnvironmentModuleNode>()  etagToModuleMap = new Map<string, EnvironmentModuleNode>()  fileToModulesMap = new Map<string, Set<EnvironmentModuleNode>>()   constructor(  environment: string,  resolveId: (url: string) => Promise<PartialResolvedId | null>,  )   async getModuleByUrl(  rawUrl: string,  ): Promise<EnvironmentModuleNode | undefined>   getModuleById(id: string): EnvironmentModuleNode | undefined   getModulesByFile(file: string): Set<EnvironmentModuleNode> | undefined   onFileChange(file: string): void   onFileDelete(file: string): void   invalidateModule(  mod: EnvironmentModuleNode,  seen: Set<EnvironmentModuleNode> = new Set(),  timestamp: number = monotonicDateNow(),  isHmr: boolean = false,  ): void   invalidateAll(): void   async ensureEntryFromUrl(  rawUrl: string,  setIsSelfAccepting = true,  ): Promise<EnvironmentModuleNode>   createFileOnlyEntry(file: string): EnvironmentModuleNode   async resolveUrl(url: string): Promise<ResolvedUrl>   updateModuleTransformResult(  mod: EnvironmentModuleNode,  result: TransformResult | null,  ): void   getModuleByEtag(etag: string): EnvironmentModuleNode | undefined }