From f428a9e67805eef7a51b81c3ccc21aff8b6bd956 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Wed, 19 Aug 2026 15:37:15 +0200 Subject: [PATCH 1/4] fix: bundle React Compiler runtime into dist Compiled modules import a bare 'react-compiler-runtime' specifier; externalizing it makes a callable 'c' depend on the consumer env, which crashes with '(0, l.c) is not a function' when it can't be resolved. Bundle the runtime into dist so it is self-contained. --- .changeset/bundle-react-compiler-runtime.md | 5 +++++ packages/react/rolldown.config.ts | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .changeset/bundle-react-compiler-runtime.md diff --git a/.changeset/bundle-react-compiler-runtime.md b/.changeset/bundle-react-compiler-runtime.md new file mode 100644 index 00000000000..19c51ba683a --- /dev/null +++ b/.changeset/bundle-react-compiler-runtime.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Bundle the React Compiler runtime (`react-compiler-runtime`) into the published output instead of importing it as an external dependency, so compiled components no longer crash in environments where the consumer cannot resolve a callable runtime. diff --git a/packages/react/rolldown.config.ts b/packages/react/rolldown.config.ts index e3c65d4e847..5e26fd3cfc8 100644 --- a/packages/react/rolldown.config.ts +++ b/packages/react/rolldown.config.ts @@ -44,9 +44,13 @@ const dependencies = [ ...Object.keys(packageMetadata.peerDependencies ?? {}), ...Object.keys(packageMetadata.dependencies ?? {}), ...Object.keys(packageMetadata.devDependencies ?? {}), -].map(name => { - return new RegExp(`^${name}(/.*)?`) -}) +] + // Bundle the React Compiler runtime into dist instead of externalizing it, so + // consumers never depend on their own environment resolving a callable `c`. + .filter(name => name !== 'react-compiler-runtime') + .map(name => { + return new RegExp(`^${name}(/.*)?`) + }) const external = [ // Exclude package dependencies From de8b3d34bf29d79c4f33a1e303e5d66d0cc945ef Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Wed, 19 Aug 2026 16:15:49 +0200 Subject: [PATCH 2/4] refactor: bundle only the compiler memo helper via a local shim Alias 'react-compiler-runtime' to a local ESM shim exporting only 'c', instead of bundling the whole CommonJS package (dead code + require interop). Also anchor the external regex to a name boundary so 'react' no longer matches 'react-compiler-runtime' (which made externalization impossible to opt out of). --- .changeset/bundle-react-compiler-runtime.md | 2 +- packages/react/rolldown.config.ts | 27 ++++++++++++++++--- packages/react/script/react-compiler.mjs | 1 + .../react/src/utils/react-compiler-runtime.ts | 27 +++++++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 packages/react/src/utils/react-compiler-runtime.ts diff --git a/.changeset/bundle-react-compiler-runtime.md b/.changeset/bundle-react-compiler-runtime.md index 19c51ba683a..8eced70cf99 100644 --- a/.changeset/bundle-react-compiler-runtime.md +++ b/.changeset/bundle-react-compiler-runtime.md @@ -2,4 +2,4 @@ '@primer/react': patch --- -Bundle the React Compiler runtime (`react-compiler-runtime`) into the published output instead of importing it as an external dependency, so compiled components no longer crash in environments where the consumer cannot resolve a callable runtime. +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 5e26fd3cfc8..bd44b7df39c 100644 --- a/packages/react/rolldown.config.ts +++ b/packages/react/rolldown.config.ts @@ -40,16 +40,36 @@ 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 ?? {}), ] - // Bundle the React Compiler runtime into dist instead of externalizing it, so - // consumers never depend on their own environment resolving a callable `c`. + // `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}(/.*)?`) + return new RegExp(`^${name}($|/)`) }) const external = [ @@ -70,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 From df71f6b7ae07aa041eafa6e9ffb9842e1349d890 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 20 Aug 2026 14:32:22 +0200 Subject: [PATCH 3/4] chore: move react-compiler-runtime to devDependencies --- package-lock.json | 23 ++++++++++++----------- packages/react/package.json | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8852950f973..b5004376e87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,8 +83,8 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@primer/react": "38.35.1", - "@primer/styled-react": "1.1.0", + "@primer/react": "38.36.0", + "@primer/styled-react": "1.1.1", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^6.0.2", @@ -97,8 +97,8 @@ "name": "example-nextjs", "version": "0.0.0", "dependencies": { - "@primer/react": "38.35.1", - "@primer/styled-react": "1.1.0", + "@primer/react": "38.36.0", + "@primer/styled-react": "1.1.1", "next": "^16.3.0", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -140,7 +140,7 @@ "version": "0.0.0", "dependencies": { "@primer/octicons-react": "^19.28.1", - "@primer/react": "38.35.1", + "@primer/react": "38.36.0", "clsx": "^2.1.1", "next": "^16.3.0", "react": "^19.2.0", @@ -22886,6 +22886,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-1.0.0.tgz", "integrity": "sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" @@ -29115,7 +29116,7 @@ }, "packages/react": { "name": "@primer/react", - "version": "38.35.1", + "version": "38.36.0", "license": "MIT", "dependencies": { "@github/mini-throttle": "^2.1.1", @@ -29135,7 +29136,6 @@ "hsluv": "1.0.1", "lodash.isempty": "^4.4.0", "lodash.isobject": "^3.0.2", - "react-compiler-runtime": "^1.0.0", "react-intersection-observer": "^10.0.3" }, "devDependencies": { @@ -29158,7 +29158,7 @@ "@storybook/addon-links": "10.5.7", "@storybook/addon-mcp": "^0.7.0", "@storybook/icons": "^2.1.0", - "@storybook/react-vite": "^10.5.7", + "@storybook/react-vite": "10.5.7", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^16.3.0", @@ -29207,6 +29207,7 @@ "postcss-preset-primer": "^0.0.0", "publint": "^0.3.15", "react": "18.3.1", + "react-compiler-runtime": "^1.0.0", "react-dom": "18.3.1", "react-is": "18.3.1", "recast": "0.23.7", @@ -30090,7 +30091,7 @@ }, "packages/styled-react": { "name": "@primer/styled-react", - "version": "1.1.0", + "version": "1.1.1", "dependencies": { "@styled-system/css": "^5.1.5", "@styled-system/props": "^5.1.5", @@ -30108,9 +30109,9 @@ "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@primer/primitives": "10.x || 11.x", - "@primer/react": "^38.26.0", + "@primer/react": "^38.36.0", "@rolldown/plugin-babel": "^0.2.3", - "@storybook/react-vite": "^10.5.7", + "@storybook/react-vite": "10.5.7", "@types/babel__core": "^7.20.5", "@types/react": "18.3.11", "@types/react-dom": "18.3.1", diff --git a/packages/react/package.json b/packages/react/package.json index 1affcd7692b..ef5510d16c6 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -90,7 +90,6 @@ "hsluv": "1.0.1", "lodash.isempty": "^4.4.0", "lodash.isobject": "^3.0.2", - "react-compiler-runtime": "^1.0.0", "react-intersection-observer": "^10.0.3" }, "devDependencies": { @@ -162,6 +161,7 @@ "postcss-preset-primer": "^0.0.0", "publint": "^0.3.15", "react": "18.3.1", + "react-compiler-runtime": "^1.0.0", "react-dom": "18.3.1", "react-is": "18.3.1", "recast": "0.23.7", From 56ea5fd09d6f5ed4ee202c219bc077fc58146a55 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 20 Aug 2026 15:05:04 +0200 Subject: [PATCH 4/4] test: cover react-compiler-runtime useMemo fallback --- .../__tests__/react-compiler-runtime.test.tsx | 33 +++++++++++++++++++ .../react/src/utils/react-compiler-runtime.ts | 4 ++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/utils/__tests__/react-compiler-runtime.test.tsx diff --git a/packages/react/src/utils/__tests__/react-compiler-runtime.test.tsx b/packages/react/src/utils/__tests__/react-compiler-runtime.test.tsx new file mode 100644 index 00000000000..b40e0b8ed0a --- /dev/null +++ b/packages/react/src/utils/__tests__/react-compiler-runtime.test.tsx @@ -0,0 +1,33 @@ +import {describe, expect, it} from 'vitest' +import {renderHook} from '@testing-library/react' +import {c, useMemoCache} from '../react-compiler-runtime' + +const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel') + +describe('react-compiler-runtime shim', () => { + it('exports a callable `c` (the compiler memo helper)', () => { + expect(typeof c).toBe('function') + }) + + describe('useMemoCache fallback', () => { + it('allocates a cache of the requested size seeded with the sentinel', () => { + const {result} = renderHook(() => useMemoCache(6)) + const cache = result.current + + expect(cache).toHaveLength(6) + for (let index = 0; index < 6; index++) { + expect(cache[index]).toBe(MEMO_CACHE_SENTINEL) + } + expect((cache as unknown as Record)[MEMO_CACHE_SENTINEL]).toBe(true) + }) + + it('reuses the same cache across re-renders', () => { + const {result, rerender} = renderHook(() => useMemoCache(4)) + const first = result.current + + rerender() + + expect(result.current).toBe(first) + }) + }) +}) diff --git a/packages/react/src/utils/react-compiler-runtime.ts b/packages/react/src/utils/react-compiler-runtime.ts index d1c286f222f..9575eda761f 100644 --- a/packages/react/src/utils/react-compiler-runtime.ts +++ b/packages/react/src/utils/react-compiler-runtime.ts @@ -9,7 +9,9 @@ const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel') type MemoCache = Array -function useMemoCache(size: number): MemoCache { +// Exported for testing: the `useMemo`-backed fallback used when React does not +// provide a built-in compiler runtime. +export function useMemoCache(size: number): MemoCache { return useMemo(() => { const cache = new Array(size) as MemoCache & Record for (let index = 0; index < size; index++) {