Changes

HMR `hotUpdate` Plugin Hook
::: tip Feedback
Give us feedback at Environment API feedback discussion
:::
We're planning to deprecate the 
handleHotUpdate plugin hook in favor of 
hotUpdate hook to be Environment API aware, and handle additional watch events with 
create and 
delete.
Affected scope: 
Vite Plugin Authors
::: warning Future Deprecation
hotUpdate was first introduced in 
v6.0. The deprecation of 
handleHotUpdate is planned for a future major. We don't recommend moving away from 
handleHotUpdate yet. If you want to experiment and give us feedback, you can use the 
future.removePluginHookHandleHotUpdate to 
"warn" in your vite config.
:::
Motivation
The 
handleHotUpdate hook allows to perform custom HMR update handling. A list of modules to be updated is passed in the 
HmrContext.
interface HmrContext {
  file: string
  timestamp: number
  modules: Array<ModuleNode>
  read: () => string | Promise<string>
  server: ViteDevServer
}
This hook is called once for all environments, and the passed modules have mixed information from the Client and SSR environments only. Once frameworks move to custom environments, a new hook that is called for each of them is needed.
The new 
hotUpdate hook works in the same way as 
handleHotUpdate but it is called for each environment and receives a new 
HotUpdateOptions instance:
interface HotUpdateOptions {
  type: 'create' | 'update' | 'delete'
  file: string
  timestamp: number
  modules: Array<EnvironmentModuleNode>
  read: () => string | Promise<string>
  server: ViteDevServer
}
The current dev environment can be accessed like in other Plugin hooks with 
this.environment. The 
modules list will now be module nodes from the current environment only. Each environment update can define different update strategies.
This hook is also now called for additional watch events and not only for 
'update'. Use 
type to differentiate between them.
Migration Guide
Filter and narrow down the affected module list so that the HMR is more accurate.
handleHotUpdate({ modules }) {
  return modules.filter(condition)
}
// Migrate to:
hotUpdate({ modules }) {
  return modules.filter(condition)
}
Return an empty array and perform a full reload:
handleHotUpdate({ server, modules, timestamp }) {
  // Invalidate modules manually
  const invalidatedModules = new Set()
  for (const mod of modules) {
    server.moduleGraph.invalidateModule(
      mod,
      invalidatedModules,
      timestamp,
      true
    )
  }
  server.ws.send({ type: 'full-reload' })
  return []
}
// Migrate to:
hotUpdate({ modules, timestamp }) {
  // Invalidate modules manually
  const invalidatedModules = new Set()
  for (const mod of modules) {
    this.environment.moduleGraph.invalidateModule(
      mod,
      invalidatedModules,
      timestamp,
      true
    )
  }
  this.environment.hot.send({ type: 'full-reload' })
  return []
}
Return an empty array and perform complete custom HMR handling by sending custom events to the client:
handleHotUpdate({ server }) {
  server.ws.send({
    type: 'custom',
    event: 'special-update',
    data: {}
  })
  return []
}
// Migrate to...
hotUpdate() {
  this.environment.hot.send({
    type: 'custom',
    event: 'special-update',
    data: {}
  })
  return []
}

Breaking Changes
List of breaking changes in Vite including API deprecations, removals, and changes. Most of the changes below can be opt-in using the 
future option in your Vite config.
Planned
These changes are planned for the next major version of Vite. The deprecation or usage warnings will guide you where possible, and we're reaching out to framework, plugin authors, and users to apply these changes.
this.environment in Hooks
HMR 
hotUpdate Plugin Hook
SSR Using 
ModuleRunner API
Considering
These changes are being considered and are often experimental APIs that intend to improve upon current usage patterns. As not all changes are listed here, please check out the Experimental Label in Vite GitHub Discussions for the full list.
We don't recommend switching to these APIs yet. They are included in Vite to help us gather feedback. Please check these proposals and let us know how they work in your use case in each's linked GitHub Discussions.
Move to Per-environment APIs
Shared Plugins During Build
Past
The changes below have been done or reverted. They are no longer relevant in the current major version.
No past changes yet

Move to Per-environment APIs
::: tip Feedback
Give us feedback at Environment API feedback discussion
:::
Multiple APIs from 
ViteDevServer related to module graph and modules transforms have been moved to the 
DevEnvironment instances.
Affected scope: 
Vite Plugin Authors
::: warning Future Deprecation
The 
Environment instance was first introduced at 
v6.0. The deprecation of 
server.moduleGraph and other methods that are now in environments is planned for a future major. We don't recommend moving away from server methods yet. To identify your usage, set these in your vite config.
future: {
  removeServerModuleGraph: 'warn',
  removeServerReloadModule: 'warn',
  removeServerPluginContainer: 'warn',
  removeServerHot: 'warn',
  removeServerTransformRequest: 'warn',
  removeServerWarmupRequest: 'warn',
}
:::
Motivation
In Vite v5 and before, a single Vite dev server always had two environments (
client and 
ssr). The 
server.moduleGraph had mixed modules from both of these environments. Nodes were connected through 
clientImportedModules and 
ssrImportedModules lists (but a single 
importers list was maintained for each). A transformed module was represented by an 
id and a 
ssr boolean. This boolean needed to be passed to APIs, for example 
server.moduleGraph.getModuleByUrl(url, ssr) and 
server.transformRequest(url, { ssr }).
In Vite v6, it is now possible to create any number of custom environments (
client, 
ssr, 
edge, etc). A single 
ssr boolean isn't enough anymore. Instead of changing the APIs to be of the form 
server.transformRequest(url, { environment }), we moved these methods to the environment instance allowing them to be called without a Vite dev server.
Migration Guide
server.moduleGraph -> 
environment.moduleGraph
server.reloadModule(module) -> 
environment.reloadModule(module)
server.pluginContainer -> 
environment.pluginContainer
server.transformRequest(url, ssr) -> 
environment.transformRequest(url)
server.warmupRequest(url, ssr) -> 
environment.warmupRequest(url)
server.hot -> 
server.client.environment.hot

Shared Plugins during Build
::: tip Feedback
Give us feedback at Environment API feedback discussion
:::
See Shared plugins during build.
Affected scope: 
Vite Plugin Authors
::: warning Future Default Change
builder.sharedConfigBuild was first introduced in 
v6.0. You can set it true to check how your plugins work with a shared config. We're looking for feedback about changing the default in a future major once the plugin ecosystem is ready.
:::
Motivation
Align dev and build plugin pipelines.
Migration Guide
To be able to share plugins across environments, plugin state must be keyed by the current environment. A plugin of the following form will count the number of transformed modules across all environments.
function CountTransformedModulesPlugin() {
  let transformedModules
  return {
    name: 'count-transformed-modules',
    buildStart() {
      transformedModules = 0
    },
    transform(id) {
      transformedModules++
    },
    buildEnd() {
      console.log(transformedModules)
    },
  }
}
If we instead want to count the number of transformed modules for each environment, we need to keep a map:
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)
    },
  }
}
To simplify this pattern, Vite exports a 
perEnvironmentState helper:
function PerEnvironmentCountTransformedModulesPlugin() {
  const state = perEnvironmentState<{ count: number }>(() => ({ count: 0 }))
  return {
    name: 'count-transformed-modules',
    perEnvironmentStartEndDuringDev: true,
    buildStart() {
      state(this).count = 0
    },
    transform(id) {
      state(this).count++
    },
    buildEnd() {
      console.log(this.environment.name, state(this).count)
    },
  }
}

SSR Using `ModuleRunner` API
::: tip Feedback
Give us feedback at Environment API feedback discussion
:::
server.ssrLoadModule has been replaced by importing from a Module Runner.
Affected scope: 
Vite Plugin Authors
::: warning Future Deprecation
ModuleRunner was first introduced in 
v6.0. The deprecation of 
server.ssrLoadModule is planned for a future major. To identify your usage, set 
future.removeSsrLoadModule to 
"warn" in your vite config.
:::
Motivation
The 
server.ssrLoadModule(url) only allows importing modules in the 
ssr environment and can only execute the modules in the same process as the Vite dev server. For apps with custom environments, each is associated with a 
ModuleRunner that may be running in a separate thread or process. To import modules, we now have 
moduleRunner.import(url).
Migration Guide
Check out the Environment API for Frameworks Guide.
server.ssrFixStacktrace and 
server.ssrRewriteStacktrace do not have to be called when using the Module Runner APIs. The stack traces will be updated unless 
sourcemapInterceptor is set to 
false.

`this.environment` in Hooks
::: tip Feedback
Give us feedback at Environment API feedback discussion
:::
Before Vite 6, only two environments were available: 
client and 
ssr. A single 
options.ssr plugin hook argument in 
resolveId, 
load and 
transform allowed plugin authors to differentiate between these two environments when processing modules in plugin hooks. In Vite 6, a Vite application can define any number of named environments as needed. We're introducing 
this.environment in the plugin context to interact with the environment of the current module in hooks.
Affected scope: 
Vite Plugin Authors
::: warning Future Deprecation
this.environment was introduced in 
v6.0. The deprecation of 
options.ssr is planned for a future major. At that point we'll start recommending migrating your plugins to use the new API. To identify your usage, set 
future.removePluginHookSsrArgument to 
"warn" in your vite config.
:::
Motivation
this.environment not only allow the plugin hook implementation to know the current environment name, it also gives access to the environment config options, module graph information, and transform pipeline (
environment.config, 
environment.moduleGraph, 
environment.transformRequest()). Having the environment instance available in the context allows plugin authors to avoid the dependency of the whole dev server (typically cached at startup through the 
configureServer hook).
Migration Guide
For the existing plugin to do a quick migration, replace the 
options.ssr argument with 
this.environment.config.consumer === 'server' in the 
resolveId, 
load and 
transform hooks:
import { Plugin } from 'vite'
export function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    resolveId(id, importer, options) {
      const isSSR = options.ssr // [!code --]
      const isSSR = this.environment.config.consumer === 'server' // [!code ++]
      if (isSSR) {
        // SSR specific logic
      } else {
        // Client specific logic
      }
    },
  }
}
For a more robust long term implementation, the plugin hook should handle for multiple environments using fine-grained environment options instead of relying on the environment name.