Skip to content

perf: compile Vite React apps with Oxc - #152

Open
adelrodriguez wants to merge 1 commit into
mainfrom
t3code/adopt-oxc-react-compiler
Open

perf: compile Vite React apps with Oxc#152
adelrodriguez wants to merge 1 commit into
mainfrom
t3code/adopt-oxc-react-compiler

Conversation

@adelrodriguez

Copy link
Copy Markdown
Collaborator

Oxc now supports React Compiler through the Vite React plugin, so the web app and desktop renderer no longer need the Babel compiler path. This moves both Vite workspaces to the native Oxc transform while keeping Expo on its supported Babel integration.

The migration uses react({ compiler: true }), removes the compiler-only Babel dependencies, and rewrites two toast defaults that caused Oxc to skip those components.

Verification:

  • bun run build --filter=app
  • bun run build --filter=desktop
  • bun test
  • bun run analyze
  • bun run check:monorepo

bun run check passes in the active repository workspace. A clean worktree check still reports the existing Astro virtual-type resolution errors in apps/docs and apps/web; both changed production builds pass from that clean worktree.

Made with GPT-5.6 in the T3 Code Codex harness.

@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
init Ready Ready Preview Aug 21, 2026 12:01am
init-docs Ready Ready Preview Aug 21, 2026 12:01am

Request Review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the full migration of the two Vite React workspaces from the Babel React Compiler path to the native Oxc path, plus the packages/ui rewrite it required and the resulting lockfile delta.

  • Plugin swapapps/app/vite.config.ts and apps/desktop/vite.renderer.config.ts replace react() + babel({ presets: [reactCompilerPreset()] }) with a single react({ compiler: true }).
  • Dependency edits — both workspaces drop @babel/core, @rolldown/plugin-babel, @types/babel__core and babel-plugin-react-compiler, bump @vitejs/plugin-react 6.0.4 → 6.1.0, and add oxc-transform-react@0.145.0.
  • Toast defaultsToastAction and ToastClose move from a destructuring default render = <Button …/> to render ?? <Button …/> inside the JSX.
  • Expo untouchedapps/mobile keeps babel-plugin-react-compiler and app.config.js reactCompiler: true, which is correct since Oxc's path is Vite-only.
  • Lockfile — the oxc-transform-react binding entries plus collateral pruning of unreferenced optional-peer resolutions.

I verified the load-bearing claims on the runner rather than taking them on trust:

  • compiler?: boolean | ReactCompilerOptions exists in the installed 6.1.0 typings, and the plugin await import("oxc-transform-react")s dynamically and hard-errors when it is absent — so the direct devDependency in both workspaces is required, not decorative.
  • Fast Refresh is not lost. vite:react-babel sets refresh: command === "serve" && !opts.compiler, and the compiler plugin instead threads jsx.refresh into the Oxc transform itself, so dev HMR parity holds.
  • File coverage is unchanged: both the old and new paths use the same include (/\.[tj]sx?$/), exclude (/\/node_modules\//) and defaultCodeFilter, and packages/ui/src/** stays covered in both because Vite follows the Bun workspace symlink to the real path.
  • The toast rewrite fixes a genuine bail, reproduced directly against oxc-transform-react: the old default emits (BuildHIR::node.lowerReorderableExpression) Expression type `JSXElement` cannot be safely reordered and leaves the component uncompiled, while the ?? form compiles and emits useMemoCache. message-scroller.tsx:105 already used this shape, so the change is consistent with the existing convention rather than new style.
  • Running the transform over all 120 compiler-eligible files in apps/app/src, apps/desktop/src and packages/ui/src produced zero remaining diagnostics — the migration is complete, not partial. bun run codegen + bun run build --filter=app pass clean and the emitted client bundle contains compiler-runtime / useMemoCache, confirming the compiler really ran in production output.

ℹ️ The render ?? <JSX/> shape is now load-bearing, and only a warning protects it

A reintroduced JSX default parameter does not fail the build — the plugin surfaces compiler diagnostics through this.warn(), and no workflow under .github/workflows/ builds any app, so a future component could silently drop out of compilation with nothing catching it. Not a blocker and not something the diff should have solved, but worth deciding on deliberately now that three packages/ui components depend on the shape.

Technical details
# React Compiler bailouts are warning-only and uncovered by CI

## Affected sites
- `packages/ui/src/components/toast.tsx:97`, `:109``render ?? <Button …/>`
- `packages/ui/src/components/message-scroller.tsx:105` — same shape, pre-existing
- `.github/workflows/` — Adamantite check, tests and Pullfrog only; nothing builds an app

## Context
`createReactCompilerPlugin` calls `this.error` only on `result.fatal`; every other
`result.errors` entry becomes `this.warn(...)`. A `render = <Button/>` default
produces a non-fatal `lowerReorderableExpression` diagnostic, so the component is
silently left uncompiled and the build still exits 0.

## Open questions for the human
- Is a guardrail wanted, or is the warning enough? A one-line note in
  `docs/agents/ui.md` explaining why `render` defaults live in the JSX rather than
  the parameter list would cover the authoring case without adding tooling.
- The `compiler` option is annotated `@experimental` in `@vitejs/plugin-react@6.1.0`
  — worth knowing for a template repo that downstream users copy, though the
  spelling and behavior are both correct today.

ℹ️ Nitpicks

  • bun.lock carries collateral changes beyond the intended dependency edits — @react-navigation/{core,native,routers}, the top-level expo-file-system 19.0.2357.0.1 hoist, files-sdk/ai@6.0.27, and the two @standard-community/*/effect subtrees. I traced each: all four are unreferenced optional peer resolutions being pruned or deduped, apps/mobile already resolved expo-file-system@57.0.1 through expo@57.0.10, and posthog-react-native@4.17.3 ships try/catch shims for both the modern and legacy file-system APIs. Nothing to change, but the PR body does not mention the drift and neither apps/mobile nor apps/api was built as part of verification.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant