Repository navigation
Conversation
h-a-n-a
force-pushed
the
feat/client-side-hmr
branch
2 times, most recently
from
July 21, 2026 03:49
3082b1b to
d4ba516
Compare
Base automatically changed from
feat/client-side-hmr
to
renovate/rolldown-related-dependencies
July 21, 2026 13:35
h-a-n-a
force-pushed
the
feat/hot-update
branch
3 times, most recently
from
July 22, 2026 13:27
1030097 to
362de1b
Compare
h-a-n-a
marked this pull request as ready for review
July 23, 2026 07:12
graphite-app Bot
pushed a commit
to rolldown/rolldown
that referenced
this pull request
Jul 28, 2026
Adds a dev-only `hotUpdate` plugin hook to the HMR engine. It works like Vite's hook of the same name. The Vite side is vitejs/vite#22956, which runs Vite's `hotUpdate` / `handleHotUpdate` hooks on top of this hook in bundled dev. ## Problem When a file changes, the engine can only find modules the graph knows about: the file's own module, and modules that watched the file with `addWatchFile`. Plugins often know more. A config file may affect many modules. A content file may not be a module at all. Vite plugins express this knowledge with the `hotUpdate` hook. Without the same hook, these plugins cannot work on the rolldown dev engine. ## Design ```mermaid flowchart LR A[file change] --> B[default affected set<br/>own module and transform deps] B --> C[hotUpdate chain<br/>each plugin may replace the set] C --> D[re-fetch, walk, patch<br/>existing steps, unchanged] ``` The hook edits the input of the update pipeline. It does not change how the pipeline works. It runs once per changed file, after the engine builds the default affected set, and before any module is re-fetched. Everything after that point — re-fetch, boundary walk, patch, delivery — stays the same. The main design rules: - **Vite behavior, engine-level types.** The chain works like Vite's: plugins run in hook order, each plugin gets the current set and may replace it, an empty array suppresses the update for this file, and no return passes the set through. But the engine uses plain module ids, not module node objects. Node objects belong to the framework layer — the adapter in vitejs/vite#22956 translates between the two. - **The engine does not override an explicit plugin choice.** Modules returned by a hook skip the "output unchanged" check. That check is only safe when "same output" means "nothing happened". That is true for a normal edit, but not for a hook result — the change may live outside the module's code. Example: a module reads `config.json` at runtime. When a plugin maps a `config.json` edit to that module, re-running the module is the whole point, and its unchanged code proves nothing. Vite always ships hook-returned modules for the same reason. - **Engine internals stay hidden.** Hooks never see lazy-compilation proxies or runtime modules, and cannot return them. Unknown ids are dropped with a debug log. A plugin cannot break the graph. - **A hook cannot block error recovery.** Files queued for retry after a failed build are added before the suppress decision. So clients stuck on an error overlay always get the recovery build. - **No hook, no cost.** If no plugin registers the hook, the whole path is skipped. If plugins return nothing, the default flow — including the "output unchanged" check — works as before. - **No `read()` on the engine hook.** Reading the changed file from disk is a framework-layer concern — the disk content may not match what the plugin pipeline produces. Per review, `read` lives only in the Vite adapter (vitejs/vite#22956), which keeps Vite's retry behavior (vitejs/vite#610). - **No time limit on the chain.** Same as Vite: a hook that never finishes blocks that update. ## What this enables Through the Vite adapter, Vite plugins with either hook run on bundled dev without changes. The browser tests in this PR cover the common plugin patterns: replacing the set (tailwind-style config mapping), buffering with `moduleGraph.invalidateModule` plus an empty return, custom messages with `read()` and `hot.send` (markdown-pages style), the legacy shared-context `read` chain (unocss with plugin-vue), file-to-submodule expansion (SFC style), and `invalidateAll` as a full rebuild plus reload. ## Tests - Unit: hidden-id filtering (runtime and lazy-proxy ids are hidden; real files and virtual modules are not). - Browser end-to-end, six playgrounds: - `hmr-hot-update-hook` — the raw hook: replace, suppress, unknown ids dropped, an empty default set for a file with no module, decline, proof that a hook-selected module ships even when its output did not change, and proof that a suppressing hook cannot block error recovery. - `hmr-hot-update-hook-chain` — each plugin receives the set as edited by the plugins before it. - `hmr-hot-update-hook-delete` — delete events reach the hook; a replacement returned for a delete ships as-is. - `hmr-hot-update-hook-error` — a throwing hook fails the round, and the failed edit is not retried. - `hmr-hot-update-hook-vite` — the Vite adapter (8 specs), covering the plugin patterns listed above. - `hmr-delete-self-watched` — a module that watches its own file adds no extra failure mode on delete. - A skipped engine test for #10487 (deleting a still-imported file on the raw engine): it asserts the desired behavior and is un-skipped by the resolver-cache fix. ## Not in this PR, planned next - Directory support for `addWatchFile` (Rollup allows directories; needed to watch files that do not exist yet). - `import.meta.glob` updates in the native plugin, built on top of that.
7 tasks done
7 tasks done
h-a-n-a
force-pushed
the
feat/hot-update
branch
from
August 3, 2026 09:55
3381052 to
1144e50
Compare
h-a-n-a
force-pushed
the
feat/hot-update
branch
from
August 3, 2026 13:44
1144e50 to
45dc235
Compare
Member
Author
|
Hold this. We might need to move |
7 tasks done
1 of 2 tasks
4 of 5 tasks
h-a-n-a
marked this pull request as draft
September 7, 2026 11:48
14 tasks
shulaoda
added a commit
to rolldown/rolldown
that referenced
this pull request
Oct 7, 2026
…tion via hotUpdate (#10550) Part of #10059 ## Problem Vite's JS plugin keeps the result of a glob fresh from a `hotUpdate` hook. Under bundled dev the native plugin replaces it, and the native plugin only implemented `transform`. #10549 makes the file events arrive, but nothing maps them to the module that owns the glob. ## What this PR does Implements `hotUpdate` in the native plugin, the way Vite's plugin does it. - Each `import.meta.glob` call gets one matcher in dev mode. It is built with globstar from the absolute globs of the call, with the options Vite passes to picomatch. `dot` follows `exhaustive`, case folding follows `caseSensitive`, and `**/node_modules/**` is excluded unless `exhaustive` is set. - The glob syntax in the literal part of a path is escaped, like Vite's `globSafeResolvedPath`. - For a created or deleted file, the hook adds every module with a matcher that matches the file to the affected set. - A content edit is declined. It cannot change which files a glob matches. The walk in `transform` is not changed. It still matches with `fast_glob`. ## Note The `hotUpdate` hook is off by default (`dev.hotUpdate`, #10837), and Vite does not pass the option yet (vitejs/vite#22956). This PR takes effect once the option is on. The hook is as simple as Vite's, so it inherits what the watcher and the engine do differently from Vite: - A file saved by renaming a temporary file over it is reported as created, so the module of the glob is updated with it. - A deleted directory is reported without its files, and no glob matches a directory. - The matchers of a deleted module stay, like in Vite, but the engine still knows the module.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes Vite's
hotUpdateandhandleHotUpdateplugin hooks work in bundled dev. Built on top of rolldown's new dev-onlyhotUpdatehook (rolldown/rolldown#10305).Problem
In bundled dev, the rolldown engine owns the module graph and the update flow. When a file changes, hooks run inside the engine, and the engine passes plain module ids. Vite plugins expect Vite's own types:
EnvironmentModuleNodeobjects, amoduleGraph, and one shared context per change event. With no translation between the two sides, every plugin that uses these hooks does nothing in bundled dev.Even a plugin as small as this had no effect before this PR:
Design
Two new pieces, both in
bundledDevHmr.ts:BundledModuleGraph) — an object that looks like Vite's module graph but stores no graph data of its own. It creates a Vite module node for an id the first time that id is asked for, then reuses the same node. Identity matters here: plugins compare nodes by reference and store their own state on them. A node'sinfo,importers, andimportedModulesare read live from the engine, so they stay correct after every rebuild. Nothing has to be kept in sync by hand. Writing to a node does nothing, because the engine owns the real graph.BundledDevHotUpdateAdapter) — every Vite plugin that has one of the two hooks gets its own wrapper, registered as that plugin's rolldownhotUpdatehook. Plugin order, and the rule that a returned module set replaces the previous one, then come from rolldown's own driver. The adapter does not re-implement them. The wrapper turns ids into nodes before it calls the Vite hook, and turns nodes back into ids after the hook returns.The main design rules:
applyToEnvironmentis a rolldown plugin. ItshotUpdatealready expects ids, so it stays unwrapped.file.vue?type=styleare included. All wrappers for that file share one options object. A change made by one plugin, for example a newreadfunction, is therefore visible to the next plugin. A post-ordered hook merges the modules buffered throughmoduleGraph.invalidateModuleand closes the state.invalidateAllrebuilds everything and reloads the page. Bundled dev has no partial invalidation to fall back on, so this is what the flag can mean here.handleHotUpdatereceives the sharedHmrContextwith mixed module nodes, runs only for updates, and warns that it is deprecated.Tests
End-to-end coverage lives in the rolldown repo's dev-server test suite (
hmr-hot-update-hook-vite, 8 browser specs). It covers the common plugin patterns:invalidateModulecalls, the way the Tailwind plugin does itread()andhot.send, the way markdown page plugins do itreadchain (unocss together with plugin-vue)invalidateAllUnit tests in this repo will follow in a later PR.