Package: radix-ui (umbrella package, e.g. ^1.4.x)
Affected component: Avatar (via use-sync-external-store)
Environment: Vite 8 / Rolldown in library mode (build.lib), ESM output (format: 'es'), React/react-dom externalized
Summary
When building a Vite component library in ESM library mode with react externalized, importing Avatar from the radix-ui umbrella package causes the bundler to include a CJS interop shim for use-sync-external-store. That shim unconditionally calls require("react") at module evaluation time, crashing any browser that loads the ESM bundle.
Error
Uncaught Error: Calling `require` for "react" in an environment that doesn't expose the `require` function.
at avatar-XXXXXXXX.esm.js:89
The crash happens immediately when the avatar chunk is evaluated — before any component renders.
Reproduction
Library (component package) — Vite config
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { glob } from "glob";
import path from "path";
export default defineConfig({
plugins: [react(),....],
server: {
port: 3002,
},
resolve: {
dedupe: ['react', 'react-dom'],
},
build: {
lib: {
entry: resolve(__dirname, 'src/main.ts'),
formats: ['es'],
},
rolldownOptions: {
external: [/^react($|\/)/, /^react-dom($|\/)/],
input: Object.fromEntries(
glob
.sync('src/**/*.{ts,tsx}', {
ignore: ['src/**/*.d.ts', 'src/_playground/**'],
})
.map((file) => {
return [
relative(
'src',
file.slice(0, file.length - extname(file).length),
),
fileURLToPath(new URL(file, import.meta.url)),
];
}),
),
output: {
entryFileNames: '[name].esm.js',
assetFileNames: 'assets/[name][extname]',
chunkFileNames: 'chunks/[name]-[hash].esm.js',
},
},
},
});
Component source — avatar.tsx
// src/components/ui/avatar.tsx
import { Avatar as AvatarPrimitive } from "radix-ui"; // ← umbrella import
function Avatar(props: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return <AvatarPrimitive.Root {...props} />;
}
function AvatarImage(
props: React.ComponentProps<typeof AvatarPrimitive.Image>,
) {
return <AvatarPrimitive.Image {...props} />;
}
...
export { Avatar, AvatarImage, .... };
Barrel export — main.ts
// src/main.ts
export * from "./components/ui/avatar";
export * from "./components/ui/dropdown-menu";
// ...
Consumer app — any file importing from the library
// Any file in the consumer app:
import { Button } from "@your-org/ui";
// ↑ Because avatar is in the barrel, avatar.esm.js is eagerly evaluated,
// triggering the require("react") call even if Avatar is never used.
What the bundler emits
The built avatar-XXXXXXXX.esm.js chunk contains roughly:
// avatar-XXXXXXXX.esm.js (simplified)
import { createRequire } from "module";
const require = createRequire(import.meta.url); // works in Node
// — or the Rolldown/Rollup guard version: —
function o(mod) {
if (typeof require !== "undefined") return require(mod);
throw new Error(
`Calling \`require\` for "${mod}" in an environment that doesn't expose the \`require\` function.`,
);
}
// use-sync-external-store CJS shim calls it immediately:
var ReactDOM = o("react"); // ← throws in browser
The shared helper chunk (dist-XXXXXXXX.esm.js) defines the o() guard and is imported by both avatar and dropdown-menu chunks — but only avatar exercises the call path via use-sync-external-store.
Potential root cause
@radix-ui/react-avatar depends on use-sync-external-store/shim. That shim ships both a CJS and an ESM variant. When the umbrella radix-ui package re-exports Avatar, some resolution paths (particularly in Vite/Rolldown with preserveModules: true) resolve the shim to its CJS variant. The CJS variant unconditionally calls require("react") during module initialization, which is incompatible with pure-ESM browser environments.
Other radix primitives that do not depend on use-sync-external-store (e.g. DropdownMenu, Button/Slot) are not affected — their chunks import the shared guard helper but never invoke it.
Impact
Any consumer app (React SPA, Next.js client component, etc.) that:
- Installs a component library built in Vite ESM library mode, and
- Imports anything from that library's barrel entry
…will crash immediately on page load, even if they never use <Avatar>.
What I have tried
Use scoped package instead of umbrella
npm install @radix-ui/react-avatar
// Before:
import { Avatar as AvatarPrimitive } from "radix-ui";
// After:
import * as AvatarPrimitive from "@radix-ui/react-avatar";
Didn't work, same issue encountered.
Expected behaviour
The umbrella radix-ui package's Avatar re-export should resolve (or force Rolldown to pick) the ESM variant of use-sync-external-store/shim, preventing any require() call from appearing in ESM output.
Environment
|
|
radix-ui |
^1.4.3 |
vite |
^8.0.x |
| Bundler |
Rolldown (Vite 8 default) |
react |
^19.x |
| Node |
22.x |
| Build format |
es (ESM library mode) |
react externalized |
Yes |
Package:
radix-ui(umbrella package, e.g.^1.4.x)Affected component:
Avatar(viause-sync-external-store)Environment: Vite 8 / Rolldown in library mode (
build.lib), ESM output (format: 'es'),React/react-domexternalizedSummary
When building a Vite component library in ESM library mode with
reactexternalized, importingAvatarfrom theradix-uiumbrella package causes the bundler to include a CJS interop shim foruse-sync-external-store. That shim unconditionally callsrequire("react")at module evaluation time, crashing any browser that loads the ESM bundle.Error
The crash happens immediately when the avatar chunk is evaluated — before any component renders.
Reproduction
Library (component package) — Vite config
Component source —
avatar.tsxBarrel export —
main.tsConsumer app — any file importing from the library
What the bundler emits
The built
avatar-XXXXXXXX.esm.jschunk contains roughly:The shared helper chunk (
dist-XXXXXXXX.esm.js) defines theo()guard and is imported by bothavataranddropdown-menuchunks — but only avatar exercises the call path viause-sync-external-store.Potential root cause
@radix-ui/react-avatardepends onuse-sync-external-store/shim. That shim ships both a CJS and an ESM variant. When the umbrellaradix-uipackage re-exportsAvatar, some resolution paths (particularly in Vite/Rolldown withpreserveModules: true) resolve the shim to its CJS variant. The CJS variant unconditionally callsrequire("react")during module initialization, which is incompatible with pure-ESM browser environments.Other radix primitives that do not depend on
use-sync-external-store(e.g.DropdownMenu,Button/Slot) are not affected — their chunks import the shared guard helper but never invoke it.Impact
Any consumer app (React SPA, Next.js client component, etc.) that:
…will crash immediately on page load, even if they never use
<Avatar>.What I have tried
Use scoped package instead of umbrella
Didn't work, same issue encountered.
Expected behaviour
The umbrella
radix-uipackage'sAvatarre-export should resolve (or force Rolldown to pick) the ESM variant ofuse-sync-external-store/shim, preventing anyrequire()call from appearing in ESM output.Environment
radix-ui^1.4.3vite^8.0.xreact^19.x22.xes(ESM library mode)reactexternalized