HMR API

Note

This is the client HMR API. For handling HMR update in plugins, see handleHotUpdate.

The manual HMR API is primarily intended for framework and tooling authors. As an end user, HMR is likely already handled for you in the framework specific starter templates.

Vite exposes its manual HMR API via the special import.meta.hot object:

  1. interface ImportMeta {
  2. readonly hot?: {
  3. readonly data: any
  4. accept(): void
  5. accept(cb: (mod: any) => void): void
  6. accept(dep: string, cb: (mod: any) => void): void
  7. accept(deps: string[], cb: (mods: any[]) => void): void
  8. dispose(cb: (data: any) => void): void
  9. decline(): void
  10. invalidate(): void
  11. on(event: string, cb: (...args: any[]) => void): void
  12. }
  13. }

Required Conditional Guard

First of all, make sure to guard all HMR API usage with a conditional block so that the code can be tree-shaken in production:

  1. if (import.meta.hot) {
  2. // HMR code
  3. }

hot.accept(cb)

For a module to self-accept, use import.meta.hot.accept with a callback which receives the updated module:

  1. export const count = 1
  2. if (import.meta.hot) {
  3. import.meta.hot.accept((newModule) => {
  4. console.log('updated: count is now ', newModule.count)
  5. })
  6. }

A module that “accepts” hot updates is considered an HMR boundary.

Note that Vite’s HMR does not actually swap the originally imported module: if an HMR boundary module re-exports imports from a dep, then it is responsible for updating those re-exports (and these exports must be using let). In addition, importers up the chain from the boundary module will not be notified of the change.

This simplified HMR implementation is sufficient for most dev use cases, while allowing us to skip the expensive work of generating proxy modules.

hot.accept(deps, cb)

A module can also accept updates from direct dependencies without reloading itself:

  1. import { foo } from './foo.js'
  2. foo()
  3. if (import.meta.hot) {
  4. import.meta.hot.accept('./foo.js', (newFoo) => {
  5. // the callback receives the updated './foo.js' module
  6. newFoo.foo()
  7. })
  8. // Can also accept an array of dep modules:
  9. import.meta.hot.accept(
  10. ['./foo.js', './bar.js'],
  11. ([newFooModule, newBarModule]) => {
  12. // the callback receives the updated modules in an Array
  13. }
  14. )
  15. }

hot.dispose(cb)

A self-accepting module or a module that expects to be accepted by others can use hot.dispose to clean-up any persistent side effects created by its updated copy:

  1. function setupSideEffect() {}
  2. setupSideEffect()
  3. if (import.meta.hot) {
  4. import.meta.hot.dispose((data) => {
  5. // cleanup side effect
  6. })
  7. }

hot.data

The import.meta.hot.data object is persisted across different instances of the same updated module. It can be used to pass on information from a previous version of the module to the next one.

hot.decline()

Calling import.meta.hot.decline() indicates this module is not hot-updatable, and the browser should perform a full reload if this module is encountered while propagating HMR updates.

hot.invalidate()

For now, calling import.meta.hot.invalidate() simply reloads the page.

hot.on(event, cb)

Listen to a custom HMR event. Custom HMR events can be sent from plugins. See handleHotUpdate for more details.