HMR(Hot Module Replacement) 
A Note to Our Readers
While maintaining fidelity to the source material, this translation incorporates explanatory content and localized expressions, informed by the translator's domain expertise. These thoughtful additions aim to enhance readers' comprehension of the original text's core messages. For any inquiries about the content, we welcome you to engage in the discussion section or consult the source text for reference.
If you're using Vite to build your project, you're likely also using Hot Module Replacement (HMR). HMR allows you to update your code without refreshing the page, such as editing component markup or adjusting styles, with changes immediately reflected in the browser. This helps speed up code interaction and improve developer experience.
While HMR is also a feature of other bundlers like Webpack and Parcel, in this article we'll dive deep into how it works in Vite. Typically, other bundlers should operate similarly.
First, it's important to note that HMR is not simple, and some topics may take some time to digest, but I hope I've piqued your interest! On this page, you'll learn:
What Conditions Are Needed for Module Replacement 
Essentially, HMR is the process of dynamically replacing modules while the application is running. Most bundlers use ECMAScript modules (ESM) as modules because it's easier to analyze module imports and exports, which helps understand how replacing one module will affect other related modules.
A module typically has access to HMR lifecycle APIs, which are used to handle operations when old modules are discarded and new modules are in place. In Vite, you have the following APIs available:
- import.meta.hot.accept()
- import.meta.hot.dispose()
- import.meta.hot.prune()
- import.meta.hot.invalidate()
Overall, they work like this:

It's important to note that you need to use these APIs for HMR to work properly. For example, Vite uses these APIs out of the box to handle CSS files, but for other files (like Vue and Svelte), you can use a Vite plugin to call these HMR APIs. Or handle them manually as needed. Otherwise, by default, updates to files will cause the entire page to reload.
Beyond that, let's dive into how these APIs work!
import.meta.hot.accept() 
When you use import.meta.hot.accept() and attach a callback, the callback will be responsible for replacing the old module with the new module. A module using this API is also called an accepted module.
Accepted modules create an HMR boundary. The HMR boundary includes the module itself and all recursively imported modules (all parent modules that depend on the current module and all their ancestor modules). The accepted module is also the "root" of the HMR boundary, as this boundary is typically a graph structure.

Accepted modules can also be narrowed down to "self-accepting modules" based on how the HMR callback is attached. import.meta.hot.accept has two function signatures:
- import.meta.hot.accept(cb: Function) - Accept changes from itself
- import.meta.hot.accept(deps: string | string[], cb: Function) - Accept changes from imported modules
If using the first signature, the current module can be called a self-accepting module. This distinction is important for HMR propagation, which we'll discuss later.
Here's how they're used:
export let data = [1, 2, 3];
if (import.meta.hot) {
  import.meta.hot.accept(newModule => {
    // Replace the old value with the new one
    data = newModule.data;
  });
}import { value } from './stuff.js';
document.querySelector('#value').textContent = value;
if (import.meta.hot) {
  import.meta.hot.accept(['./stuff.js'], ([newModule]) => {
    // Re-render with the new value
    document.querySelector('#value').textContent = newModule.value;
  });
}import.meta.hot.dispose() 
When an accepted module or a module accepted by other modules is being replaced with a new module, or is being removed, we can use import.meta.hot.dispose() for cleanup. This allows us to clean up any side effects created by the old module, such as removing event listeners, clearing timers, or resetting state.
Here's an example of the API:
globalThis.__my_lib_data__ = {};
if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    // Reset global state
    globalThis.__my_lib_data__ = {};
  });
}import.meta.hot.prune() 
When a module needs to be completely removed from runtime, such as when a file is deleted, we can use import.meta.hot.prune() to perform final cleanup. This is similar to import.meta.hot.dispose(), but is only called once when the module is removed.
Internally, Vite prunes modules at different stages through import analysis (analyzing module imports), because the only time we can know a module is no longer in use is when it's no longer imported (depended on) by any other module.
Here's an example of Vite using the CSS HMR API:
// Import utilities to update/remove style tags in the HTML
import { removeStyle, updateStyle } from '/@vite/client';
updateStyle('/src/style.css', 'body { color: red; }');
if (import.meta.hot) {
  // Empty accept callback is we want to accept, but we don't have to do anything.
  // `updateStyle` will automatically get rid of the old style tag.
  import.meta.hot.accept();
  // Remove style when the module is no longer used
  import.meta.hot.prune(() => {
    removeStyle('/src/style.css');
  });
}import.meta.hot.invalidate() 
Unlike the above APIs, import.meta.hot.invalidate() is an operation rather than a lifecycle hook. You typically use it inside import.meta.hot.accept when you might realize at runtime that a module cannot be safely updated and needs to bail out.
When this method is called, the Vite server will be notified that the module is invalid, as if it had been updated. HMR propagation will execute again to determine if any of its importers can recursively accept this change.
Here's an example of the API:
export const data = [1, 2, 3];
if (import.meta.hot) {
  import.meta.hot.accept(newModule => {
    // If the `data` export is deleted or renamed
    if (!(data in newModule)) {
      // Bail out and invalidate the module
      import.meta.hot.invalidate();
    }
  });
}Other HMR APIs 
The Vite HMR documentation covers more APIs. However, they are not key to understanding how HMR works, so we'll skip them for now, but we'll return to these APIs when discussing the HMR client later.
If you're interested in how they can be useful in certain situations, please take a quick read of the documentation!
From the Beginning 
From the above content, we've learned about the HMR APIs and how these APIs allow developers to replace and manage modified modules. But there's still a missing piece: how do we know when to replace a module? HMR typically occurs after editing a file, but what happens after that?
At first glance, the situation looks something like this:

Let's explain the specific process in detail.
Editing a file 
HMR begins when you edit a file and save it. A file system watcher like chokidar detects the change and passes the edited file path to the next step.
Processing edited modules 
The Vite dev server receives the edited file path information. With these edited file paths, it finds the corresponding module information using the module dependency graph. It's important to note that "file" and "module" are two different concepts - a file may correspond to one or multiple modules. For example, a Vue file can be compiled into a JavaScript module and related CSS modules.
These modules are then passed to the handleHotUpdate() hook of Vite plugins for further processing. Plugins can choose to filter or extend the array of modules needed. The final modules will be passed to the next step.
Here are some plugin examples:
// Example: filter out array of modules
function vuePlugin() {
  return {
    name: 'vue',
    async handleHotUpdate(ctx) {
      if (ctx.file.endsWith('.vue')) {
        const oldContent = cache.get(ctx.file);
        const newContent = await ctx.read();
        // If only the style has changed when editing the file, we can filter
        // out the JS module and only trigger the CSS module for HMR.
        if (isOnlyStyleChanged(oldContent, newContent)) {
          return ctx.modules.filter(m => m.url.endsWith('.css'));
        }
      }
    }
  };
}// Example: extending array of modules
function globalCssPlugin() {
  return {
    name: 'global-css',
    handleHotUpdate(ctx) {
      if (ctx.file.endsWith('.css')) {
        // If a CSS file is edited, we also trigger HMR for this special
        // `virtual:global-css` module that needs to be re-transformed.
        const mod = ctx.server.moduleGraph.getModuleById(
          'virtual:global-css'
        );
        if (mod) {
          return ctx.modules.concat(mod);
        }
      }
    }
  };
}Module invalidation 
Before HMR propagation, we eagerly recursively invalidate the final array of updated modules and their importers. Each module's transformed code will be removed, and a timestamp will be appended. This timestamp will be used to get the new module on the client side the next time it's requested.
HMR propagation 
To update the final array of modules now, HMR propagation will be executed. This is where the "magic" happens, and it's often the source of confusion that causes HMR to not work as expected.
HMR propagation is essentially about starting with the module to be updated to retrieve the required HMR boundary. If all modules to be updated are within a boundary, the Vite dev server will notify the HMR client that the accepted module should execute HMR. If some are not within the boundary, then a full page reload will be triggered.
To better understand how it works, let's look at this example by case:

- Scenario 1: If updated - stuff.js, propagation will recursively find its parent dependency modules and all their ancestor dependency modules to find an accepted module. In this case, we'll find that- app.jsxis an accepted module. But before ending propagation, we need to determine if- app.jsxcan accept changes from- stuff.js. This depends on how- import.meta.hot.accept()is called.- Scenario 1(a): If app.jsxis self-accepting, or it accepts changes fromstuff.js, we can stop continuing up the propagation, because no other module depends onstuff.jsmodule. Then theHMR clientwill notifyapp.jsxto executeHMR.
- Scenario 1(b): If app.jsxdoesn't acceptstuff.jsmodule changes, then we'll continue up the propagation to find an accepted module. But since there's no other accepted module, we'll reach "root"index.htmlfile. This will trigger a full page reload.
 
- Scenario 1(a): If 
- Scenario 2: If updated - main.jsor- other.js, then propagation will again recursively find its parent dependency modules and all their ancestor dependency modules. However, we'll find no accepted module, and we'll reach "root" index.html file. Therefore, a full page reload will be triggered.
- Scenario 3: If updated - app.jsx, then we immediately find it as an accepted module. However, some modules may not be able to update themselves. We can determine if an accepted module is self-accepting to determine if an accepted module can update itself.- Scenario 3(a): If app.jsxis self-accepting, we can stop here and let theHMR clientnotify this module to executeHMR.
- Scenario 3(b): If app.jsxis not self-accepting, we'll continue up the propagation to find an accepted module. But since we didn't find any, and we'll reach "root" index.html file, this will trigger a full page reload.
 
- Scenario 3(a): If 
- Scenario 4: If updated - utils.js, propagation will again recursively find its parent dependency modules and all their ancestor dependency modules. First, we'll find- app.jsxas an accepted module, and we'll stop propagation there (assuming it meets Scenario 1(a)). Then we'll also recursively find- other.jsand its parent dependency modules and all their ancestor dependency modules, but we'll find no accepted module, and we'll reach "root" index.html file. If- at least onemodule is not accepted, then a full page reload will be triggered.
If you want to learn about more advanced scenarios involving multiple HMR boundaries, please click on the foldable part below:
Details
Switching to advanced scenarios, let's look at a different example involving three .jsx files in 3 HMR boundaries:

- Scenario 5: If updated - stuff.js, propagation will recursively find its importers to find an accepted module. We'll find that- comp.jsxis an accepted module, and we'll handle it the same way as Scenario 1. To reiterate:- Scenario 5(a): If comp.jsxis self-accepting, orcomp.jsxaccepts changes fromstuff.js, then we can stop propagation. Then theHMR clientwill notifycomp.jsxto executeHMR.
- Scenario 5(b): If comp.jsxdoesn't accept this change, we'll continue up the propagation to find an accepted module. We'll find thatapp.jsxis an accepted module, and we'll handle it the same way as Scenario 5! Until we find a module that can acceptstuff.jschanges, if there's a branch that retrieves "root" index.html, then we need to perform a full page load.
 
- Scenario 5(a): If 
- Scenario 6: If updated - bar.js, propagation will recursively find its importers and find that- comp.jsxand- alert.jsxare accepted modules. We'll also handle these two modules the same way as Scenario 5. Assuming the best case is that both- comp.jsxand- alert.jsxmodules are Scenario 5(a), that is, both receive- bar.jsupdates, then the- HMR clientwill notify- comp.jsxand- alert.jsxtwo modules to execute- HMRtogether.
- Scenario 7: If updated - utils.js, propagation will again recursively find its importers(importers) and find all direct importers- comp.jsx,- alert.jsx, and- app.jsx, and all are accepted modules. We'll also handle these three modules(- comp.jsx,- alert.jsx, and- app.jsx) the same way as Scenario 5. Assuming the best case is all accepted modules are Scenario 5(a), even if- comp.jsxis also a- HMR boundarypart of- app.jsx, the- HMR clientwill notify them three to execute- HMR. (Future, Vite may detect this and only notify- app.jsxand- alert.jsx, but this is largely an implementation detail!)
- Scenario 8: If updated - comp.jsx, we immediately find it as an accepted module. Like Scenario 3, we need to first check if- comp.jsxis self-accepting.- Scenario 8(a): If comp.jsxis self-accepting, then we stop propagation and let theHMR clientnotifycomp.jsxto executeHMR.
- Scenario 8(b): If comp.jsxis not self-accepting, then we can handle it the same way as Scenario 5(b).
 
- Scenario 8(a): If 
Besides the above content, there are many other edge cases not covered here because they're a bit complex, including circular imports, partial accepted modules, only CSS importers, etc. However, as you become more familiar with the whole process, you can revisit them!
Finally, the result of HMR propagation is to determine whether to perform a full page load or should execute HMR in the client.
If it's determined that a full page reload is needed, then a message will be sent to the HMR client and told to reload the page.
If it's determined that a module can be hot updated, then in the HMR propagation period all accepted modules that meet the condition are synthesized array sent to the HMR client, the client will trigger the HMR APIs we discussed above, thus executing HMR.
How Does This HMR Client Work? 
In a Vite application, you may notice that Vite adds a special script to the HTML to request /@vite/client script. This script contains the logic for handling the HMR client.
The HMR client is responsible for the following:
- Establish a WebSocketconnection with theVitedev server.
- Listen for HMR payloadsfrom theViteserver.
- Provide and trigger HMR APIsin the runtime phase.
- Send any events back to the Vitedev server.
From a broader perspective, the HMR client helps establish the connection between the Vite dev server and the HMR APIs. Let's see how this connection works.

Client initialization 
Before the HMR client can receive any messages from the Vite dev server, it needs to first establish a connection through WebSockets. Here's an example of setting up a WebSocket connection for further processing the results of HMR propagation from the Vite dev server:
// /@vite/client (URL)
const ws = new WebSocket('ws://localhost:5173');
ws.addEventListener('message', ({ data }) => {
  const payload = JSON.parse(data);
  switch (payload.type) {
    case '...':
    // Handle payloads...
  }
});
// Send any events to the Vite dev server
ws.send('...');In the next section, we'll discuss payload processing in more detail.
Besides, the HMR client also initializes some state for handling HMR and exports several APIs, such as createHotContext(), for modules using HMR APIs. For example:
// Injected by Vite's import-analysis plugin
import { createHotContext } from '/@vite/client';
import.meta.hot = createHotContext('/src/app.jsx');
export default function App() {
  return <div>Hello World</div>;
}
// Injected by `@vitejs/plugin-react`
if (import.meta.hot) {
  // ...
}The URL string (also called "owner path") passed to createHotContext() helps determine which module can accept changes. Internally, createHotContext will assign registered HMR callbacks to a singleton mapping, which will contain all mappings from owner path to accept callbacks, dispose callback, and prune callback. We'll explain this in more detail later!
This is basically how modules interact with the HMR client and execute HMR changes.
Handling payloads from the server 
After establishing a WebSocket connection, we can start handling valid callbacks from the Vite dev server.
// /@vite/client (URL)
ws.addEventListener('message', ({ data }) => {
  const payload = JSON.parse(data);
  switch (payload.type) {
    case 'full-reload': {
      location.reload();
      break;
    }
    case 'update': {
      const updates = payload.updates;
      // => { type: string, path: string, acceptedPath: string, timestamp: number }[]
      for (const update of updates) {
        handleUpdate(update);
      }
      break;
    }
    case 'prune': {
      handlePrune(payload.paths);
      break;
    }
    // Handle other payload types...
  }
});The above example handles the results of HMR propagation, determining whether to trigger full page reload or HMR update, depending on the full-reload and update callback parameters to distinguish. It also handles pruning when the module is no longer used.
Callback parameters also have other properties, not all of which are for HMR but briefly mentioned:
- connected: Sent when a WebSocket connection is established.
- error: Sent when an error occurs on the server side, Vitecan display the specific error content in the browser console.
- custom: Sent by Viteplugins to notify client any events. It's useful for interconnection between client and server.
Continuing forward, let's see how HMR update works in practice.
HMR update 
Each HMR boundary found in HMR propagation usually corresponds to an HMR update. In Vite, updates take this signature:
interface Update {
  // The type of update
  type: 'js-update' | 'css-update';
  // The URL path of the accepted module (HMR boundary root)
  path: string;
  // The URL path that is accepted (usually the same as above)
  // (We'll talk about this later)
  acceptedPath: string;
  // The timestamp when the update happened
  timestamp: number;
}Different HMR implementations can freely reshape the above update signature. In Vite, Update is divided into js update or css update.
css update is specially handled as simply swapping link tags in the HTML.
For js update, we need to find the corresponding new module and call its import.meta.hot.accept() callback function, through the callback function to apply HMR to itself. Since we've registered the path as the first parameter in createHotContext() (i.e., determining the mapping between path and execution function at page execution time), then we can easily find the matching module through the updated path and get the latest module information according to the updated timestamp, and pass the new module to the import.meta.hot.accept() callback function. The implementation logic is simplified as follows:
// /@vite/client (URL)
// Map populated by `createHotContext()`
const ownerPathToAcceptCallbacks = new Map<string, Function[]>();
async function handleUpdate(update: Update) {
  const acceptCbs = ownerPathToAcceptCallbacks.get(update.path);
  const newModule = await import(
    `${update.acceptedPath}?t=${update.timestamp}`
  );
  for (const cb of acceptCbs) {
    cb(newModule);
  }
}However, it's important to note that import.meta.hot.accept() has two function signatures?
- import.meta.hot.accept(cb: Function) 
- import.meta.hot.accept(deps: string | string[], cb: Function) 
The above implementation logic only applies to the first function signature (i.e., self-accepting module), but not to the second. The callback of the second function signature is only called when dependency updates occur. We can bind each callback to a set of dependencies, like this:
// URL: /src/app.jsx
import { add } from './utils.js';
import { value } from './stuff.js';
if (import.meta.hot) {
  import.meta.hot.accept(/** ... */);
  // { deps: ['/src/app.jsx'], fn: ... }
  import.meta.hot.accept('./utils.js' /** ... */);
  // { deps: ['/src/utils.js'], fn: ... }
  import.meta.hot.accept(['./stuff.js'] /** ... */);
  // { deps: ['/src/stuff.js'], fn: ... }
}Then we can use acceptedPath to match dependencies and trigger the correct callback function. For example, if updated stuff.js, then acceptedPath will be /src/stuff.js, and path will be /src/app.jsx. We can adjust the HMR processing program as follows:
// Map populated by `createHotContext()`
const ownerPathToAcceptCallbacks = new Map<
  string,
  { deps: string[]; fn: Function }[]
>()
async function handleUpdate(update: Update) {
  const acceptCbs = ownerPathToAcceptCallbacks.get(update.path)
  const newModule = await import(`${update.acceptedPath}?t=${update.timestamp}`)
  for (const cb of acceptCbs) {
    // Make sure to only execute callbacks that can handle `acceptedPath`
    if (cb.deps.some((deps) => deps.includes(update.acceptedPath))) {
      cb.fn(newModule)
    }
  }
}But it's not over. Before importing the new module, we also need to ensure old modules are handled correctly through import.meta.hot.dispose().
// Maps populated by `createHotContext()`
const ownerPathToAcceptCallbacks = new Map<
  string,
  { deps: string[]; fn: Function }[]
>()
const ownerPathToDisposeCallback = new Map<string, Function>()
async function handleUpdate(update: Update) {
  const acceptCbs = ownerPathToAcceptCallbacks.get(update.path)
  // Call the dispose callback if there's any
  ownerPathToDisposeCallbacks.get(update.path)?.()
  const newModule = await import(`${update.acceptedPath}?t=${update.timestamp}`)
  for (const cb of acceptCbs) {
    // Make sure to only execute callbacks that can handle `acceptedPath`
    if (cb.deps.some((deps) => deps.includes(update.acceptedPath))) {
      cb.fn(newModule)
    }
  }
}Then we basically have most of what the HMR client needs to do! As further practice, you can also try to implement error handling, empty owner check, queue and parallel update for predictability, etc., which will make the final form more robust.
HMR pruning 
As discussed in import.meta.hot.prune(), Vite handles HMR pruning internally in the "import analysis" stage. When a module is no longer imported by any other module, the Vite dev server will send a { type: 'prune', paths: string[] } object to the HMR client, and the HMR client will independently prune these invalid modules at runtime.
// /@vite/client (URL)
// Maps populated by `createHotContext()`
const ownerPathToDisposeCallback = new Map<string, Function>();
const ownerPathToPruneCallback = new Map<string, Function>();
function handlePrune(paths: string[]) {
  for (const p of paths) {
    ownerPathToDisposeCallbacks.get(p)?.();
    ownerPathToPruneCallback.get(p)?.();
  }
}HMR invalidation 
Unlike other HMR APIs, import.meta.hot.invalidate() is an API that can be called inside import.meta.hot.accept() when you might realize at runtime that a module cannot be safely updated and needs to bail out. In /@vite/client, simply sending a WebSocket message to the Vite dev server is enough:
// /@vite/client (URL)
// `ownerPath` comes from `createHotContext()`
function handleInvalidate(ownerPath: string) {
  ws.send(
    JSON.stringify({
      type: 'custom',
      event: 'vite:invalidate',
      data: { path: ownerPath }
    })
  );
}When the Vite server receives this request, it will again execute HMR propagation, starting from its importers, and send the result (**full reload** or execute HMR update) back to the HMR client.
HMR events 
Although not required for HMR, the HMR client can also emit events at runtime when receiving specific payloads. import.meta.hot.on and import.meta.hot.off can be used to listen and unlisten to these events.
if (import.meta.hot) {
  import.meta.hot.on('vite:invalidate', () => {
    // ...
  });
}Emitting and tracking these events is very similar to how we handle the above HMR callbacks. Taking HMR invalidation code as an example:
const eventNameToCallbacks = new Map<string, Set<Function>>();
// `ownerPath` comes from `createHotContext()`
function handleInvalidate(ownerPath: string) {
  eventNameToCallbacks.get('vite:invalidate')?.forEach((cb) => cb());
  ws.send(
    JSON.stringify({
      type: 'custom',
      event: 'vite:invalidate',
      data: { path: ownerPath }
    })
  );
}HMR data 
Finally, the HMR client also provides a way to store data to be shared between HMR APIs using import.meta.hot.data. This data can be seen passed to the HMR callback functions of import.meta.hot.dispose() and import.meta.hot.prune().
Storing data is similar to how we track HMR callbacks. Taking the HMR pruning code as an example:
// Maps populated by `createHotContext()`
const ownerPathToDisposeCallback = new Map<string, Function>();
const ownerPathToPruneCallback = new Map<string, Function>();
const ownerPathToData = new Map<string, Record<string, any>>();
function handlePrune(paths: string[]) {
  for (const p of paths) {
    const data = ownerPathToData.get(p);
    ownerPathToDisposeCallbacks.get(p)?.(data);
    ownerPathToPruneCallback.get(p)?.(data);
  }
}Summary 
That's all about HMR! In short, we learned:
- How to use HMR APIsto handle file changes.
- How file editing causes the Vite dev serverto sendHMR updatesto theHMR client.
- How the HMR clientprocessesHMR payloadsand triggers the correctHMR APIs.
Before we end, please check out the FAQ below if you still have questions about how certain things work.
FAQ 
- Where can I find the source code for - Vite's- HMRimplementation?- vite/src/client/client.ts - Implementation source code for /@vite/client.
- vite/src/shared/hmr.ts - The HMR client implementation used by /@vite/client. Also abstracted away for the HMR client in SSR. (See HMRClient)
- vite/src/node/server/hmr.ts - Handle HMR propagation. (See handleHMRUpdate)
 
- Are there any - HMR examplesto learn from?- HMRis typically implemented by- JS frameworksthat introduce the concept of "components", where each component can isolate its state and reinitialize itself. Therefore, you can look at how frameworks like- React,- Vue, and- Svelteimplement them:- React:Fast Refresh and @vitejs/plugin-react 
- Vue:@vue/runtime-core and @vitejs/plugin-vue 
- Svelte:svelte-hmr and @vitejs/plugin-svelte 
 
- How does - Vite's implementation differ from- Webpackand other tools?- I haven't delved deep into - Webpack's implementation, only understanding- Webpack's- HMRprinciples from the- Webpackdocumentation and this- NativeScriptarticle. As far as I know, a common difference is that- Webpackhandles- HMR propagationon the client side rather than the server side.- This difference has a benefit: - HMR APIscan be used more dynamically. In contrast,- Viteneeds to statically parse the- HMR APIsused in modules on the server side to determine if a module has called- import.meta.hot.accept(). However, handling- HMR propagationon the client side can be complex because some important information (such as importers, exports, ids, etc.) only exists on the server. Due to the need for this important information, it might require restructuring to get serialized module information on the client side and keep it synchronized with the server, which could be complex.
- How does HMR work in server-side rendering? - At the time of writing this article ( - Vite 5.0),- HMRin- SSRis not yet supported, but will be released as an experimental feature in- Vite 5.1. Even without- HMRin- SSR, for- JSframeworks like- Vueand- Svelte, you can still get- HMRon the client side.- Making changes to server-side code requires completely re-executing the SSR entry point, which can be triggered through - HMR propagation(this also applies to- SSR). But typically,- HMR propagationfor server-side code will cause a full page reload, which is perfect for the client to re-send requests to the server and have the server perform re-execution.
- How to trigger a page reload in handleHotUpdate()? - The - handleHotUpdate()API is designed to handle modules to be invalidated or handle- HMRpropagation. However, there may be cases where changes are detected that require an immediate page reload.- In - Vite, you can use- server.ws.send({ type: 'full-reload' })to trigger a full page reload, and to ensure modules are invalidated without- HMR propagation(which might incorrectly cause unnecessary- HMR), you can use- server.moduleGraph.invalidateModule().js- function reloadPlugin() { return { name: 'reload', handleHotUpdate(ctx) { if (ctx.file.includes('/special/')) { // Trigger page reload ctx.server.ws.send({ type: 'full-reload' }); // Invalidate the modules ourselves const invalidatedModules = new Set(); for (const mod of ctx.modules) { ctx.server.moduleGraph.invalidateModule( mod, invalidatedModules, ctx.timestamp, true ); } // Don't return any modules so HMR doesn't happen, // and because we already invalidated above return []; } } }; }
- Is there any specification for HMR APIs? - The only specification I know of is mentioned in this document, which has been archived. - Viteinitially implemented this specification but later deviated slightly, for example,- import.meta.hot.decline()was not implemented.- If you're interested in implementing your own - HMR API, you might need to choose between versions like- Viteor- Webpack. But essentially, the terminology for accepting and invalidating changes will remain the same.
- Are there other resources for learning about HMR? - Besides the Hot Module Replacement (HMR) documentation for Vite, Webpack, and Parcel, there aren't many resources that dive deep into how - HMRactually works. However, here are a few resources I found helpful:
 XiSenao
 XiSenao SenaoXi
 SenaoXi