diff --git a/.changeset/bundle-react-compiler-runtime.md b/.changeset/bundle-react-compiler-runtime.md new file mode 100644 index 00000000000..8eced70cf99 --- /dev/null +++ b/.changeset/bundle-react-compiler-runtime.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Bundle the React Compiler memo helper (`c`) into `@primer/react` via a local ESM shim instead of importing it from the external `react-compiler-runtime` package. This prevents a runtime crash (`TypeError: (0, l.c) is not a function`) when a consumer's bundle cannot resolve a callable `c`. diff --git a/packages/react/rolldown.config.ts b/packages/react/rolldown.config.ts index e3c65d4e847..bd44b7df39c 100644 --- a/packages/react/rolldown.config.ts +++ b/packages/react/rolldown.config.ts @@ -40,13 +40,37 @@ function getEntrypointsFromInput(input: ReadonlySet) { ) } +// The React Compiler emits `import {c} from 'react-compiler-runtime'`. Alias it +// to a local ESM shim that is bundled into the output, so the memo helper is +// self-contained instead of an external CommonJS dependency that can fail to +// resolve a callable `c` in a consumer's bundle. +const reactCompilerRuntimeShim = path.resolve('src/utils/react-compiler-runtime.ts') + +function reactCompilerRuntimeAlias() { + return { + name: 'react-compiler-runtime-alias', + resolveId(source: string) { + if (source === 'react-compiler-runtime') { + return {id: reactCompilerRuntimeShim, external: false} + } + return null + }, + } +} + const dependencies = [ ...Object.keys(packageMetadata.peerDependencies ?? {}), ...Object.keys(packageMetadata.dependencies ?? {}), ...Object.keys(packageMetadata.devDependencies ?? {}), -].map(name => { - return new RegExp(`^${name}(/.*)?`) -}) +] + // `react-compiler-runtime` is aliased to a local shim and bundled, so it must + // not be external. + .filter(name => name !== 'react-compiler-runtime') + // Anchor to a package-name boundary so a name isn't treated as a prefix of + // another (e.g. `react` must not match `react-compiler-runtime`). + .map(name => { + return new RegExp(`^${name}($|/)`) + }) const external = [ // Exclude package dependencies @@ -66,6 +90,7 @@ export default defineConfig([ { input, plugins: [ + reactCompilerRuntimeAlias(), babel({ include: /\.(?:js|jsx|ts|tsx)$/, exclude: /node_modules/, diff --git a/packages/react/script/react-compiler.mjs b/packages/react/script/react-compiler.mjs index 01de9a0fbbd..95455a8f350 100644 --- a/packages/react/script/react-compiler.mjs +++ b/packages/react/script/react-compiler.mjs @@ -30,6 +30,7 @@ const unsupportedPatterns = [ 'src/hooks/useResizeObserver.ts', 'src/hooks/useSafeTimeout.ts', 'src/TooltipV2/Tooltip.tsx', + 'src/utils/react-compiler-runtime.ts', ] const unsupported = new Set( diff --git a/packages/react/src/utils/react-compiler-runtime.ts b/packages/react/src/utils/react-compiler-runtime.ts new file mode 100644 index 00000000000..d1c286f222f --- /dev/null +++ b/packages/react/src/utils/react-compiler-runtime.ts @@ -0,0 +1,27 @@ +import React, {useMemo} from 'react' + +// Local replacement for the `c` helper from `react-compiler-runtime`. The build +// aliases the compiler's `import {c} from 'react-compiler-runtime'` to this file +// so only `c` is bundled (as clean ESM), instead of externalizing the whole +// CommonJS package. Mirrors the upstream behavior: prefer React's built-in +// compiler runtime (React 19+), otherwise fall back to a `useMemo`-backed cache. +const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel') + +type MemoCache = Array + +function useMemoCache(size: number): MemoCache { + return useMemo(() => { + const cache = new Array(size) as MemoCache & Record + for (let index = 0; index < size; index++) { + cache[index] = MEMO_CACHE_SENTINEL + } + cache[MEMO_CACHE_SENTINEL] = true + return cache + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) +} + +const builtinRuntime = (React as typeof React & {__COMPILER_RUNTIME?: {c?: (size: number) => MemoCache}}) + .__COMPILER_RUNTIME + +export const c: (size: number) => MemoCache = typeof builtinRuntime?.c === 'function' ? builtinRuntime.c : useMemoCache