Accessing current environment in code
The current environment instance is usually available as part of the context of the code being run, so accessing environments through server.environments should be rare. Inside plugin hooks, the environment is exposed as part of PluginContext and can be accessed using this.environment.
DevEnvironment.fetchModule method
The fetchModule(id: string, importer?: string, options?: FetchFunctionOptions) method is called by the module runner to retrieve information about a specified module. It internally calls transformRequest and wraps the result in the format the module runner understands. Returns Promise<FetchResult>. This method is not meant to be called manually.
DevEnvironmentContext interface
DevEnvironmentContext has the following properties: hot (boolean, required), transport (HotChannel | WebSocketServer, optional), options (EnvironmentOptions, optional), remoteRunner (object with optional inlineSourceMap boolean), and depsOptimizer (DepsOptimizer, optional).
TransformResult interface
TransformResult has the following properties: code (string, required), map (SourceMap | { mappings: '' } | null, required), etag (string, optional), deps (string[], optional), and dynamicDeps (string[], optional).
Accessing environments from dev server
During dev, available environments in a dev server can be accessed using server.environments. For example, server.environments.client or server.environments.ssr. Each environment is an instance of the DevEnvironment class.
EnvironmentModuleNode class
EnvironmentModuleNode represents a single module in the module graph with: environment (string), url (string), id (string | null, default null), file (string | null, default null), type ('js' | 'css'), importers (Set<EnvironmentModuleNode>), importedModules (Set<EnvironmentModuleNode>), importedBindings (Map<string, Set<string>> | null), info (ModuleInfo, optional), meta (Record<string, any>, optional), transformResult (TransformResult | null), acceptedHmrDeps (Set<EnvironmentModuleNode>), acceptedHmrExports (Set<string> | null), isSelfAccepting (boolean, optional), lastHMRTimestamp (number, default 0), and lastInvalidationTimestamp (number, default 0).
EnvironmentModuleGraph class
EnvironmentModuleGraph represents the module graph for a single environment with: environment (string), urlToModuleMap (Map<string, EnvironmentModuleNode>), idToModuleMap (Map<string, EnvironmentModuleNode>), etagToModuleMap (Map<string, EnvironmentModuleNode>), fileToModulesMap (Map<string, Set<EnvironmentModuleNode>>). Constructor takes environment (string) and resolveId (function returning Promise<PartialResolvedId | null>).
EnvironmentModuleGraph methods
EnvironmentModuleGraph has the following methods: getModuleByUrl(rawUrl: string) returns Promise<EnvironmentModuleNode | undefined>; getModuleById(id: string) returns EnvironmentModuleNode | undefined; getModulesByFile(file: string) returns Set<EnvironmentModuleNode> | undefined; onFileChange(file: string) returns void; onFileDelete(file: string) returns void; invalidateModule(mod: EnvironmentModuleNode, seen?: Set<EnvironmentModuleNode>, timestamp?: number, isHmr?: boolean) returns void; invalidateAll() returns void; ensureEntryFromUrl(rawUrl: string, setIsSelfAccepting?: boolean) returns Promise<EnvironmentModuleNode>; createFileOnlyEntry(file: string) returns EnvironmentModuleNode; resolveUrl(url: string) returns Promise<ResolvedUrl>; updateModuleTransformResult(mod: EnvironmentModuleNode, result: TransformResult | null) returns void; getModuleByEtag(etag: string) returns EnvironmentModuleNode | undefined.
Separate module graphs per environment
Each environment has an isolated module graph with the same signature, allowing generic algorithms to crawl or query the graph without depending on the environment. When a file is modified, the module graph of each environment is used independently to discover affected modules and perform HMR.
Vite v5 vs v6 module graph differences
Vite v5 had a mixed Client and SSR module graph where unprocessed or invalidated nodes couldn't be identified as Client, SSR, or both. Module nodes had prefixed properties like clientImportedModules and ssrImportedModules, with importedModules returning the union. The importers property contained all importers from both environments. Module nodes had separate transformResult and ssrTransformResult. A backward compatibility layer allows migration from deprecated server.moduleGraph.
DevEnvironment class - core properties
The DevEnvironment class represents a single environment instance during dev. It has: name (unique identifier for the environment in a Vite server, defaults to 'client' or 'ssr'), hot (NormalizedHotChannel for communication with the module runner), moduleGraph (EnvironmentModuleGraph with imported relationships and cached processed code), plugins (resolved plugins including per-environment created ones), pluginContainer (EnvironmentPluginContainer to resolve, load, and transform code), and config (ResolvedConfig with options at server global scope as defaults for all environments, can override resolve conditions, external, optimizedDeps).
DevEnvironment.warmupRequest method
The warmupRequest(url: string) method registers a request to be processed with low priority to avoid waterfalls. The Vite server has information about imported modules from other requests and can warm up the module graph so modules are already processed when requested. Returns Promise<void>.
DevEnvironment.transformRequest method
The transformRequest(url: string) method resolves a URL to an id, loads it, and processes the code using the plugins pipeline. The module graph is updated during this process. It returns Promise<TransformResult | null>.
Example registering environment in config hook
Example of registering a new environment in the config hook:
```ts
config(config: UserConfig) {
return {
environments: {
rsc: {
resolve: {
conditions: ['react-server', ...defaultServerConditions],
},
},
},
}
}
```
This registers an rsc environment with react-server condition for React Server Components support.
Example accessing environment config in transform hook
Example of accessing environment configuration in a plugin hook:
```ts
transform(code, id) {
console.log(this.environment.config.resolve.conditions)
}
```
This shows how to access the resolve.conditions from the current environment's config within a transform hook.
Example applyToEnvironment hook
Example of applyToEnvironment hook:
```js
const UnoCssPlugin = () => {
// shared global state
return {
buildStart() {
// init per-environment state with WeakMap<Environment,Data>
// using this.environment
},
configureServer() {
// use global hooks normally
},
applyToEnvironment(environment) {
// return true if this plugin should be active in this environment,
// or return a new plugin to replace it.
// if the hook is not used, the plugin is active in all environments
},
resolveId(id, importer) {
// only called for environments this plugin apply to
},
}
}
```
Example perEnvironmentPlugin helper usage
Example of using the perEnvironmentPlugin helper:
```js
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
perEnvironmentPlugin('per-environment-plugin', (environment) =>
nonShareablePlugin({ outputName: environment.name }),
),
],
})
```
This simplifies wrapping a non-shareable plugin when no other hooks require special handling.
Example wrapping non-environment-aware plugin
Example of wrapping a non-environment-aware plugin using applyToEnvironment:
```js
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
{
name: 'per-environment-plugin',
applyToEnvironment(environment) {
return nonShareablePlugin({ outputName: environment.name })
},
},
],
})
```
This isolates a non-shareable plugin by creating separate instances per environment.
Global hooks vs per-environment hooks
Plugin hooks fall into two categories: global hooks are called once for the whole server, handling app-wide concerns like config resolution and server setup, with no relevant this.environment. Per-environment hooks are called once for each environment and expose the current environment through this.environment in their context. All Rolldown hooks are per-environment, as are other Vite-specific hooks that handle modules. buildStart and buildEnd are only called for the client environment without the perEnvironmentStartEndDuringDev: true flag.
Accessing current environment in plugin hooks
Plugin hooks now expose this.environment in their context. APIs that previously expected a ssr boolean are now scoped to the proper environment. For example, environment.moduleGraph.getModuleByUrl(url) replaces the earlier pattern of server.moduleGraph.getModuleByUrl(url, { ssr }). A plugin can access environment options and configuration through the environment instance.
Register new environments in config hook
Plugins can add new environments in the config hook by returning an environments object. An empty object is enough to register the environment using default values from the root level environment config. Example: returning { environments: { rsc: { resolve: { conditions: ['react-server', ...defaultServerConditions] } } } } registers an rsc environment with custom resolve conditions.
configEnvironment hook for per-environment configuration
The configEnvironment hook allows plugins to configure each environment individually. Type: (name: string, config: EnvironmentOptions, env: { mode: string, command: 'build' | 'serve', isSsrBuild?: boolean, isPreview?: boolean, isSsrTargetWebworker?: boolean }) => EnvironmentOptions | null | void. Kind: async, sequential. Scope: per-environment. It is called for each environment with its partially resolved config including resolution of final defaults. The hook is useful when the complete list of environments isn't yet known during the config hook.
Per-environment state in plugins using Map
When the same plugin instance is used for different environments, plugin state should be keyed with this.environment. A Map<Environment, State> pattern can keep state for each environment separately. This matches the existing ecosystem pattern of using the ssr boolean as a key to avoid mixing client and ssr modules state.
buildStart and buildEnd called only for client without flag
For backward compatibility, buildStart and buildEnd are only called for the client environment without the perEnvironmentStartEndDuringDev: true flag. Similarly, watchChange is only called for certain environments without the perEnvironmentWatchChangeDuringDev: true flag. Plugins can opt-in to per-environment calls by setting these flags to true.
applyToEnvironment hook type and scope
The applyToEnvironment hook type is: (environment: PartialEnvironment) => boolean | PluginOption | Promise<boolean>. Kind: async, sequential. Scope: per-environment. It allows a plugin to define which environments it should apply to. The hook can return true to apply the plugin to an environment, false to skip it, or a new plugin to replace it.
applyToEnvironment hook for environment-aware plugins
A plugin can define what environments it applies to with the applyToEnvironment function. If a plugin is not environment-aware and has state not keyed on the current environment, the applyToEnvironment hook allows it to be easily made per-environment. Returning a new plugin instance configured for a specific environment enables isolation of non-shareable plugin state.
perEnvironmentPlugin helper for wrapping plugins
Vite exports a perEnvironmentPlugin helper to simplify wrapping plugins that require per-environment isolation. Usage: perEnvironmentPlugin('plugin-name', (environment) => wrappedPlugin({ outputName: environment.name })). This helper applies the plugin only to specific environments as a shorthand for implementing applyToEnvironment when no other hooks need special handling.
applyToEnvironment hook timing during config
The applyToEnvironment hook is called at config time, currently after configResolved due to ecosystem projects modifying plugins in configResolved. Environment plugins resolution may be moved before configResolved in the future.
Example per-environment plugin with Map state
Example of a per-environment plugin using Map to track state:
```js
function PerEnvironmentCountTransformedModulesPlugin() {
const state = new Map<Environment, { count: number }>()
return {
name: 'count-transformed-modules',
perEnvironmentStartEndDuringDev: true,
buildStart() {
state.set(this.environment, { count: 0 })
},
transform(id) {
state.get(this.environment).count++
},
buildEnd() {
console.log(this.environment.name, state.get(this.environment).count)
}
}
}
```
This example shows using perEnvironmentStartEndDuringDev: true flag and keying state by this.environment.
Example configEnvironment hook
Example of configEnvironment hook:
```ts
configEnvironment(name: string, options: EnvironmentOptions) {
// add "workerd" condition to the rsc environment
if (name === 'rsc') {
return {
resolve: {
conditions: ['workerd'],
},
}
}
}
```
This hook adds workerd condition to an rsc environment's resolve conditions.
Environment factory configuration pattern
Environment factories wrap default configuration and merge it with user config. Example: function createWorkerdEnvironment(userConfig) { 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); }
Environment factories purpose and usage
Environment factories are implemented by runtime providers, not end users. They return EnvironmentOptions for the most common case of using the target runtime for both dev and build environments. Environment factories allow framework authors and end users to not have to set up runtime integration themselves.
Default Vite environments
A Vite dev server exposes two environments by default: a client environment and an ssr environment. The client environment is a browser environment by default. The SSR environment runs in the same Node runtime as the Vite server by default and allows application servers to be used to render requests during dev with full HMR support.
Module and module graph definition
Transformed source code is called a module. The relationships between modules processed in each environment are kept in a module graph. The transformed code for these modules is sent to the runtimes associated with each environment to be executed. When a module is evaluated in the runtime, its imported modules will be requested triggering the processing of a section of the module graph.
Module Runner purpose and difference from server.ssrLoadModule
A Vite Module Runner allows running any code by processing it with Vite plugins first. It is different from server.ssrLoadModule because the runner implementation is decoupled from the server. This allows library and framework authors to implement their layer of communication between the Vite server and the runner. The browser communicates with its environment using WebSocket and HTTP requests. The Node Module runner can directly do function calls. Other environments could run modules connecting to runtimes like workerd or Worker Threads.
DevEnvironment communication levels
There are multiple communication levels for the DevEnvironment. To make it easier for frameworks to write runtime agnostic code, it is recommended to implement the most flexible communication level possible.
Worker thread environment factory example
Example showing how to create a DevEnvironment for worker threads: function createWorkerEnvironment(name, config, context) { const worker = new Worker('./worker.js'); const handlerToWorkerListener = new WeakMap(); const workerHotChannel = { skipFsCheck: true, send: (data) => worker.postMessage(data), on: (event, handler) => { /* implementation */ }, off: (event, handler) => { /* implementation */ } }; return new DevEnvironment(name, config, { transport: workerHotChannel }); }
Environment API release status
The Environment API is in release candidate phase. The APIs are planned to be stabilized in a future major release once downstream projects have experimented with them. Some specific APIs are still considered experimental.
EnvironmentOptions interface structure
The EnvironmentOptions interface includes: define (Record<string, any>, optional), resolve (EnvironmentResolveOptions, optional), optimizeDeps (DepOptimizationOptions, required), consumer (literal 'client' or 'server', optional), dev (DevOptions, required), and build (BuildOptions, required).
UserConfig extends EnvironmentOptions
The UserConfig interface extends from the EnvironmentOptions interface, allowing configuration of the client and defaults for other environments through the environments option.
Client and ssr environments during dev
During dev, the client and a server environment named ssr are always present. This allows backward compatibility with server.ssrLoadModule(url) and server.moduleGraph.
Custom environment instances for runtime providers
Low level configuration APIs are available so runtime providers can provide environments with proper defaults for their runtimes. These environments can spawn other processes or threads to run modules during dev in a closer runtime to the production environment.
Cloudflare Vite plugin uses Environment API
The Cloudflare Vite plugin uses the Environment API to run code in the Cloudflare Workers runtime (workerd) during development by configuring a custom ssr environment.
Current Vite server API backward compatible
The current Vite server API is not yet deprecated and is backward compatible with Vite 5. The server.moduleGraph returns a mixed view of the client and ssr module graphs, with backward compatible mixed module nodes returned from all its methods.
Adoption recommendation for Environment API
Switching to Environment API is not yet recommended. Vite aims for a good portion of the user base to adopt Vite 6 before so plugins do not need to maintain two versions.
Guide target users for Environment API
The Environment API guide provides basic concepts for end users. Plugin authors should reference the Environment API Plugins Guide, framework authors should reference the Environment API Frameworks Guide, and runtime providers should reference the Environment API Runtimes Guide.
Vite 6 Environment API release candidate status
The Environment API is in release candidate phase. Vite plans to stabilize these APIs with potential breaking changes in a future major release once downstream projects have validated them. Some specific APIs are still experimental, and the ecosystem is encouraged to provide feedback.
Environments formalized in Vite 6
Vite 6 formalizes the concept of Environments. Until Vite 5, there were two implicit environments: client and optionally ssr. The new Environment API allows users and framework authors to create as many environments as needed to map how their apps work in production.
Simple SPA/MPA configuration unchanged
For a simple SPA/MPA, no new APIs around environments need to be exposed in the config. Internally, Vite applies the options to a client environment, but users do not need to know this concept when configuring Vite. The Vite 5 config and behavior work seamlessly.
Vite dev server runs multiple environments concurrently
During dev, a single Vite dev server can run code in multiple different environments concurrently. Each environment is independent, configured to match the production environment as closely as possible, and connected to a dev runtime where code is executed.
Multi-environment configuration with environments option
Apps composed of several environments can be configured explicitly with the environments config option. Each environment entry can override or extend top-level options.
Environment option inheritance from top-level config
When not explicitly documented, an environment inherits configured top-level config options. A small number of top-level options like optimizeDeps only apply to the client environment. These options are marked with a NonInheritBadge in the config reference.
Client environment configuration via top-level options recommended
The client environment can be configured explicitly through environments.client, but it is recommended to use top-level options so the client config remains unchanged when adding new environments.
Built-in import.meta.env constants
Vite exposes four built-in constants in import.meta.env in all cases: import.meta.env.MODE (string) - the mode the app is running in; import.meta.env.BASE_URL (string) - the base url the app is being served from, determined by the base config option; import.meta.env.PROD (boolean) - whether the app is running in production; import.meta.env.DEV (boolean) - whether the app is running in development, always the opposite of PROD; import.meta.env.SSR (boolean) - whether the app is running in the server.
VITE_ prefix exposes variables to client-side code
Environment variables prefixed with VITE_ will be exposed in client-side source code after Vite bundling as strings. Variables without this prefix will not be exposed to the client. For example, VITE_SOME_KEY=123 will be accessible as import.meta.env.VITE_SOME_KEY returning "123", while DB_PASSWORD=foobar will be undefined in client code.
Customize env variables prefix with envPrefix option
The envPrefix config option can be used to customize which environment variables are exposed to client-side code instead of using the default VITE_ prefix.
VITE_ variables are parsed as strings
Environment variables prefixed with VITE_ are parsed and exposed as strings, even if they represent numbers or booleans. For example, VITE_SOME_KEY=123 will return the string "123", not the number 123. You must convert to the desired type when using it in your code.
Do not put sensitive information in VITE_ variables
VITE_* variables should not contain sensitive information such as API keys because the values are bundled into your source code at build time and are publicly visible. For production deployments, use a backend server or serverless/edge functions to properly secure secrets.
Env file naming convention and loading order
Vite uses dotenv to load environment variables from files in the environment directory in this order: .env (loaded in all cases), .env.local (loaded in all cases, ignored by git), .env.[mode] (only loaded in specified mode), .env.[mode].local (only loaded in specified mode, ignored by git).
Env file priority and precedence
An env file for a specific mode (e.g. .env.production) takes higher priority than a generic one (e.g. .env). Vite always loads .env and .env.local in addition to the mode-specific .env.[mode] file. Variables declared in mode-specific files take precedence over those in generic files, but variables defined only in .env or .env.local are still available. Environment variables that already exist when Vite is executed have the highest priority and will not be overwritten by .env files. For example, when running VITE_SOME_KEY=123 vite build, the command-line value takes precedence.