vite icon indicating copy to clipboard operation
vite copied to clipboard

fix(plugin-container): add defensive check for undefined transform handler

Open Aditya-Prakash14 opened this issue 1 month ago • 1 comments

Description

This PR fixes an intermittent crash that causes the error Cannot read properties of undefined (reading 'call') during HMR and server restarts.

Fixes #21162

Problem

This error occurs intermittently and repeatedly, primarily when:

  • Making significant code changes (mass formatting or refactoring)
  • Switching branches
  • Modifying package.json

The error appears in both the browser and terminal, effectively stopping the HMR process and requiring a manual server restart. This is particularly common with Astro + Tailwind + React setups.

Root Cause

  1. vite-plugin-react intentionally removes its transform hook during the configResolved phase for performance optimization (source)
  2. During server restarts (especially triggered by package.json changes), the pluginContainer may iterate over cached plugin references where the transform hook has been dynamically deleted
  3. getHookHandler(plugin.transform) returns undefined, and attempting to call it throws the error

Credit to @sapphi-red for identifying the root cause.

Solution

Added a defensive check in pluginContainer.ts before calling the transform handler:

const handler = getHookHandler(plugin.transform)
if (!handler) {
  continue
}

Aditya-Prakash14 avatar Dec 01 '25 07:12 Aditya-Prakash14

Thanks for the question, happy to clarify.

What this sentence is trying to describe is a race between the dev server restart and in-flight transform calls. When package.json changes, Vite fully recreates the dev server: a new server instance (with fresh plugin instances) is created, the old server is closed, and properties from the new server are assigned back onto the existing server object. During this transition:

  • The old pluginContainer can still be running pending transform calls that started before the restart. These calls hold references to the old plugin objects.
  • Some plugins (like vite-plugin-react) intentionally delete or replace their transform hook during configResolved for performance reasons, so on those old plugin objects plugin.transform may already have been removed.

If pluginContainer iterates over its internal plugin list while one of those stale plugin objects no longer has a valid transform hook, getHookHandler(plugin.transform) returns undefined, and the subsequent handler.call(...) throws Cannot read properties of undefined (reading 'call'). The defensive check simply turns this into a no-op for that plugin in this edge case, letting the rest of the pipeline continue without crashing.

Aditya-Prakash14 avatar Dec 02 '25 10:12 Aditya-Prakash14

Each pluginContainer has a separate cache. So I don't think your explanation makes sense. https://github.com/vitejs/vite/blob/3f344b4f13f867fa646e0562f62c7bed06549cb0/packages/vite/src/node/server/pluginContainer.ts#L212

sapphi-red avatar Dec 03 '25 11:12 sapphi-red