Don't trigger a full page reload for scanned files that Vite processes as modules - #20336
Conversation
Tailwind uses the `addWatchFile` API from Vite to make sure that Vite triggers when any of these files change. However, we don't want to process/transform these files, we just want the plugin to be triggered so we can generate new CSS. This caused some problems in the past where a change to an asset file required the user to reload the page (e.g. a php file in case of Laravel) instead of reloading automatically. We made sure that if it's _not_ an asset file, aka a file that is handled by Vite or any of its plugins that we don't use the `full-reload` trigger, and instead rely on normal HMR. Now another issue came up where a JS or TS file that is not part of the Vite tree _yet_ is also considered an asset file since it's not handled yet. This can happen if you use code splitting where a module isn't loaded yet. Because it's an asset file, we will trigger a full-reload, which is not what we want. Instead we check whether it's a JS-like or CSS-like file because those are always handled by Vite. If we detect those, we rely on normal HMR. If it's a file such as an .astro file or .vue file, then we check if any other file has the same extension, and if it does, then we know that Vite would handle this file correctly, and we rely on HMR again.
Confidence Score: 5/5The change is safe to merge. It only makes the The heuristics are well-layered: hardcoded families (JS/TS, CSS) are checked cheaply before the module graph scan, the scan itself is bounded to one pass per new extension type via the No files require special attention. The implementation in Reviews (1): Last reviewed commit: "update changelog" | Re-trigger Greptile |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe Vite plugin now detects files transformed as modules across Vite environments and caches confirmed extensions before deciding whether external changes require a full reload. New integration tests cover unloaded 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
This PR fixes an issue where editing a scanned file that Vite (or one of its plugins) can process as a module, but that isn't currently loaded, caused
@tailwindcss/viteto force a full page reload, throwing away all client state.The
hotUpdatehook has a fallback that sends afull-reloadfor files that Tailwind scans but that Vite knows nothing about (e.g..phpor.blade.phptemplates rendered by a backend). Without it, edits to those files wouldn't refresh the page at all. To detect those files we check whether every module for the changed file is anassetand/or has no id, because the scanner'saddWatchFilecalls create exactly such placeholder nodes for every scanned file.The problem is that a source file that Vite can process, but that isn't loaded yet, looks exactly the same. The realistic way to get into that state is code splitting: with route-level splitting (e.g.
React.lazy, TanStack Router'sautoCodeSplitting, lazy routes invue-router), every component behind an un-visited split boundary only exists as a scan placeholder in the module graph. Editing any of them reloaded the whole app. The same happens for component stylesheets that a framework plugin compiles into the component (e.g. Angular via Analog), which never show up as their own module.A full reload is never useful for these files: if the file is loaded, Vite's own HMR handles it, and if it isn't loaded, reloading the page won't load it either. Any new candidates still apply through the regular
css-updateflow because the file is registered viaaddWatchFile.So instead, we now skip the fallback when the changed file is handled by Vite's module pipeline:
.vue,.svelte, or.mdwith an SSG plugin), a file with the same extension exists as a real module in some environment's module graph, then a plugin does handle this file type and the changed file just isn't loaded (yet).External templates like
.phpfiles still trigger a full reload exactly like before.Fixes: #20320
Fixes: #19903
Closes: #20323
Test plan
.phpfile still triggers afull-reload