fix(server-hmr): metadata routes overwrite page runtime HMR handler#92273
fix(server-hmr): metadata routes overwrite page runtime HMR handler#92273wbinnssmith merged 11 commits intocanaryfrom
Conversation
Tests Passed |
d730ad5 to
ca4c9bd
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
Stats from current PR✅ No significant changes detected📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📎 Tarball URL |
Turbopack loads separate runtime chunks for app pages and metadata routes (robots.ts, sitemap.ts, etc.) in the same Node.js process. Each chunk registers a __turbopack_server_hmr_apply__ handler that is bound to its own moduleFactories/devModuleCache. Previously each runtime just assigned to globalThis.__turbopack_server_hmr_apply__, so the last chunk to load would silently win. Navigating to /robots.txt before an HMR update meant only the metadata route runtime received the update; the page appeared frozen. Fix by using a multicast registry: each runtime appends its own handler to globalThis.__turbopack_server_hmr_handlers__[], and a shared dispatcher installed on first registration calls all of them. On full cache reset, hot-reloader-turbopack resets the array so stale handlers from evicted chunks don't accumulate. Co-Authored-By: Claude <[email protected]>
ca4c9bd to
3051225
Compare
|
in client side code we prevent multiple copies of the runtime from loading with a runtime test turbopack/crates/turbopack-ecmascript-runtime/src/browser_runtime.rs see the should we do the same thing server side? do we really need multiple copies of the server runtime to be loaded? |
I briefly took a look at merging the module caches and runtimes but it seemed pretty difficult given they don't share the same chunking context (ssr vs rsc modules). @sokra how possible is this? |
Co-Authored-By: Claude <[email protected]>
| let applied = false | ||
| for (const fn of fns) { | ||
| try { | ||
| if (fn(update)) applied = true |
There was a problem hiding this comment.
should we break, after the first runtime accepts?
also if we have different chunking contexts can we filter to the expected runtime based on asset prefixes. just wondering if this will cause us to install modules from the wrong runtime.
`!fns` is unreachable (globalThis.__turbopack_server_hmr_handlers__ is always set to an array before any handler call). Replace it with a comment explaining why we do not break on the first accepting runtime and why per-runtime moduleFactories closures make asset-prefix filtering unnecessary. Co-Authored-By: Claude <[email protected]>
…hmr-react-compiler
80ffbfb to
7c5a384
Compare
The registry was reset to [] after a require cache clear, but dev-nodejs.ts now stores handlers in a Map (keyed by __filename for routing by chunkPrefix). Reading the [] back via ?? new Map() failed because ?? doesn't treat [] as nullish, so .set() was called on an Array, crashing on page reload. Co-Authored-By: Claude <[email protected]>
…ewrite The sourcesContent in the runtime source map was still referencing the old Array-based multicast implementation. Updated to reflect the current Map-based routing dispatcher introduced in 7c5a384. Co-Authored-By: Claude <[email protected]>
…t single-line update The previous snapshot commit wrote a single-line JSON blob, but the Rust snapshot test framework expects the multi-line per-section format. Restore the correct format while keeping the updated dev-nodejs.ts sourcesContent/mappings for the Map-based routing dispatcher. Co-Authored-By: Claude <[email protected]>
…y for toCall - Replace startsWith prefix matching with path.dirname exact comparison so chunk updates are routed to the runtime whose output directory contains that specific chunk, not any runtime whose prefix is a prefix of the chunk path. - Change toCall from Set<HmrHandlerEntry> to HmrHandlerEntry[] as suggested. A seen-key Set handles deduplication when multiple chunk paths fall in the same runtime directory. - Update both snapshot files to match the new compiled output. Co-Authored-By: Claude <[email protected]>
…rbopack output
Turbopack's sections_to_rope generates {"offset": {"line": N, "column": 0}
with spaces, but a previous Python-based snapshot update wrote compact JSON
{"offset":{"line":N,"column":0} without spaces. Rewrite the file using the
exact format the Rust serializer produces.
Co-Authored-By: Claude <[email protected]>
Use the test framework's own snapshot recording instead of manual Python reconstruction to ensure exact format parity. Co-Authored-By: Claude <[email protected]>
…92273) ### What? Fix server HMR becoming unresponsive after a metadata route is loaded in the same Node.js process as an app page. ### Why? Turbopack loads separate runtime chunks for app pages and metadata routes (`robots.ts`, `sitemap.ts`, `manifest.ts`, `icon.tsx`, etc.) in the same Node.js process. Each runtime chunk embeds `dev-nodejs.ts` and produces a distinct `__turbopack_server_hmr_apply__` closure bound to its own `moduleFactories` and `devModuleCache`. Previously each runtime simply overwrote `globalThis.__turbopack_server_hmr_apply__`, so the last chunk to load silently won. Navigating to `/robots.txt` before an HMR update caused the metadata route runtime to overwrite the page runtime's handler. Subsequent HMR updates were dispatched only to the metadata route runtime, which has no knowledge of the page module — the page appeared frozen and stopped reflecting file changes. ### How? Replace the bare assignment with a multicast registry: 1. Each runtime appends its own `__turbopack_server_hmr_apply__` handler to `globalThis.__turbopack_server_hmr_handlers__[]`. 2. The first runtime to register installs a shared dispatcher as `globalThis.__turbopack_server_hmr_apply__` that iterates all registered handlers at call time (not install time), so newly loaded runtimes are always included. 3. On full cache reset, `hot-reloader-turbopack.ts` resets `__turbopack_server_hmr_handlers__` to `[]` so stale handlers from evicted chunks don't accumulate into the next generation. Because `dev-nodejs.ts` is embedded into the Turbopack binary via `include_dir!`, this fix requires rebuilding the native binary. ### Tests `test/development/app-dir/server-hmr/server-hmr.test.ts` — extended with `metadata route hmr` tests that load a metadata route before patching a page file, verifying HMR updates still reach the page runtime after a second runtime chunk is loaded. --------- Co-authored-by: Will Binns-Smith <[email protected]> Co-authored-by: Claude <[email protected]>
…92273) ### What? Fix server HMR becoming unresponsive after a metadata route is loaded in the same Node.js process as an app page. ### Why? Turbopack loads separate runtime chunks for app pages and metadata routes (`robots.ts`, `sitemap.ts`, `manifest.ts`, `icon.tsx`, etc.) in the same Node.js process. Each runtime chunk embeds `dev-nodejs.ts` and produces a distinct `__turbopack_server_hmr_apply__` closure bound to its own `moduleFactories` and `devModuleCache`. Previously each runtime simply overwrote `globalThis.__turbopack_server_hmr_apply__`, so the last chunk to load silently won. Navigating to `/robots.txt` before an HMR update caused the metadata route runtime to overwrite the page runtime's handler. Subsequent HMR updates were dispatched only to the metadata route runtime, which has no knowledge of the page module — the page appeared frozen and stopped reflecting file changes. ### How? Replace the bare assignment with a multicast registry: 1. Each runtime appends its own `__turbopack_server_hmr_apply__` handler to `globalThis.__turbopack_server_hmr_handlers__[]`. 2. The first runtime to register installs a shared dispatcher as `globalThis.__turbopack_server_hmr_apply__` that iterates all registered handlers at call time (not install time), so newly loaded runtimes are always included. 3. On full cache reset, `hot-reloader-turbopack.ts` resets `__turbopack_server_hmr_handlers__` to `[]` so stale handlers from evicted chunks don't accumulate into the next generation. Because `dev-nodejs.ts` is embedded into the Turbopack binary via `include_dir!`, this fix requires rebuilding the native binary. ### Tests `test/development/app-dir/server-hmr/server-hmr.test.ts` — extended with `metadata route hmr` tests that load a metadata route before patching a page file, verifying HMR updates still reach the page runtime after a second runtime chunk is loaded. --------- Co-authored-by: Will Binns-Smith <[email protected]> Co-authored-by: Claude <[email protected]>
…92273) ### What? Fix server HMR becoming unresponsive after a metadata route is loaded in the same Node.js process as an app page. ### Why? Turbopack loads separate runtime chunks for app pages and metadata routes (`robots.ts`, `sitemap.ts`, `manifest.ts`, `icon.tsx`, etc.) in the same Node.js process. Each runtime chunk embeds `dev-nodejs.ts` and produces a distinct `__turbopack_server_hmr_apply__` closure bound to its own `moduleFactories` and `devModuleCache`. Previously each runtime simply overwrote `globalThis.__turbopack_server_hmr_apply__`, so the last chunk to load silently won. Navigating to `/robots.txt` before an HMR update caused the metadata route runtime to overwrite the page runtime's handler. Subsequent HMR updates were dispatched only to the metadata route runtime, which has no knowledge of the page module — the page appeared frozen and stopped reflecting file changes. ### How? Replace the bare assignment with a multicast registry: 1. Each runtime appends its own `__turbopack_server_hmr_apply__` handler to `globalThis.__turbopack_server_hmr_handlers__[]`. 2. The first runtime to register installs a shared dispatcher as `globalThis.__turbopack_server_hmr_apply__` that iterates all registered handlers at call time (not install time), so newly loaded runtimes are always included. 3. On full cache reset, `hot-reloader-turbopack.ts` resets `__turbopack_server_hmr_handlers__` to `[]` so stale handlers from evicted chunks don't accumulate into the next generation. Because `dev-nodejs.ts` is embedded into the Turbopack binary via `include_dir!`, this fix requires rebuilding the native binary. ### Tests `test/development/app-dir/server-hmr/server-hmr.test.ts` — extended with `metadata route hmr` tests that load a metadata route before patching a page file, verifying HMR updates still reach the page runtime after a second runtime chunk is loaded. --------- Co-authored-by: Will Binns-Smith <[email protected]> Co-authored-by: Claude <[email protected]>
What?
Fix server HMR becoming unresponsive after a metadata route is loaded in the same Node.js process as an app page.
Why?
Turbopack loads separate runtime chunks for app pages and metadata routes (
robots.ts,sitemap.ts,manifest.ts,icon.tsx, etc.) in the same Node.js process. Each runtime chunk embedsdev-nodejs.tsand produces a distinct__turbopack_server_hmr_apply__closure bound to its ownmoduleFactoriesanddevModuleCache.Previously each runtime simply overwrote
globalThis.__turbopack_server_hmr_apply__, so the last chunk to load silently won. Navigating to/robots.txtbefore an HMR update caused the metadata route runtime to overwrite the page runtime's handler. Subsequent HMR updates were dispatched only to the metadata route runtime, which has no knowledge of the page module — the page appeared frozen and stopped reflecting file changes.How?
Replace the bare assignment with a multicast registry:
__turbopack_server_hmr_apply__handler toglobalThis.__turbopack_server_hmr_handlers__[].globalThis.__turbopack_server_hmr_apply__that iterates all registered handlers at call time (not install time), so newly loaded runtimes are always included.hot-reloader-turbopack.tsresets__turbopack_server_hmr_handlers__to[]so stale handlers from evicted chunks don't accumulate into the next generation.Because
dev-nodejs.tsis embedded into the Turbopack binary viainclude_dir!, this fix requires rebuilding the native binary.Tests
test/development/app-dir/server-hmr/server-hmr.test.ts— extended withmetadata route hmrtests that load a metadata route before patching a page file, verifying HMR updates still reach the page runtime after a second runtime chunk is loaded.