From 3d6b5fd4b66e8e3f749c049a8dbe9707c0925260 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 14 May 2026 04:05:03 -0600 Subject: [PATCH] fix(vite): merge @radix-ui into react chunk to break load-order cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cloud.tangle.tools was hard-broken (`TypeError: Cannot read properties of undefined (reading 'forwardRef')`, only the purple background shell visible) because the post-#3199/#3203 rebuild produced this chunk graph: react-*.js ──imports `_getDefaultExportFromCjs` from──▶ radix-*.js radix-*.js ──top-level Qu.reduce → Yu → `i.forwardRef`──▶ react-*.js Rollup hoists the CJS-interop helper into whichever chunk happens to own the right modules under the current content-hash layout. On this build it landed in radix, so react imported it back, forming a cycle that crashed at module init: radix's top-level Primitive.* reduce ran before react's named exports were initialised. Fix: co-locate react/react-dom with @radix-ui in a single chunk. @radix-ui already depends on react, so they must evaluate in this order anyway — putting them together makes the chunk graph acyclic. Verified locally on both apps: - tangle-cloud: react chunk has zero cross-chunk imports → leaf - tangle-dapp: react chunk imports only from `noble` (also a leaf) - Both apps mount cleanly under preview with no pageerror events --- apps/tangle-cloud/vite.config.ts | 14 ++++++++++++-- apps/tangle-dapp/vite.config.ts | 11 +++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/tangle-cloud/vite.config.ts b/apps/tangle-cloud/vite.config.ts index ce4d2ec22..e92543b51 100644 --- a/apps/tangle-cloud/vite.config.ts +++ b/apps/tangle-cloud/vite.config.ts @@ -72,13 +72,23 @@ export default defineConfig({ if (id.includes('viem')) return 'viem'; if (id.includes('@tanstack')) return 'tanstack'; if (id.includes('react-router')) return 'router'; + // Co-locate react/react-dom with @radix-ui. Splitting them lets + // Rollup hoist the CJS-interop helper (`_getDefaultExportFromCjs`) + // into whichever chunk evaluates first; on some content-hash + // layouts the helper landed in the radix chunk and the react + // chunk imported it back, forming a load-order cycle that + // crashed the app at module init with + // `Cannot read properties of undefined (reading 'forwardRef')` + // when radix's top-level `Qu.reduce(...)` ran before react's + // bindings had initialised. Co-resident avoids the cycle since + // radix depends on react and must evaluate after it anyway. if ( id.includes('node_modules/react/') || - id.includes('node_modules/react-dom/') + id.includes('node_modules/react-dom/') || + id.includes('@radix-ui') ) { return 'react'; } - if (id.includes('@radix-ui')) return 'radix'; return undefined; }, }, diff --git a/apps/tangle-dapp/vite.config.ts b/apps/tangle-dapp/vite.config.ts index ca4c0153d..8692fe9dc 100644 --- a/apps/tangle-dapp/vite.config.ts +++ b/apps/tangle-dapp/vite.config.ts @@ -166,13 +166,20 @@ export default defineConfig({ if (id.includes('node_modules/globalthis/')) return 'utils'; if (id.includes('@tanstack')) return 'tanstack'; if (id.includes('react-router')) return 'router'; + // Co-locate react/react-dom with @radix-ui. See tangle-cloud's + // vite.config.ts for the full incident write-up — splitting + // react from radix lets Rollup hoist the CJS-interop helper + // into the radix chunk, forming a load-order cycle that crashes + // the app with `Cannot read properties of undefined (reading + // 'forwardRef')` when radix's top-level `Qu.reduce(...)` runs + // before react's bindings have initialised. if ( id.includes('node_modules/react/') || - id.includes('node_modules/react-dom/') + id.includes('node_modules/react-dom/') || + id.includes('@radix-ui') ) { return 'react'; } - if (id.includes('@radix-ui')) return 'radix'; return undefined; }, },