perf: compile Vite React apps with Oxc - #152
Conversation
There was a problem hiding this comment.
✅ 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 swap —
apps/app/vite.config.tsandapps/desktop/vite.renderer.config.tsreplacereact()+babel({ presets: [reactCompilerPreset()] })with a singlereact({ compiler: true }). - Dependency edits — both workspaces drop
@babel/core,@rolldown/plugin-babel,@types/babel__coreandbabel-plugin-react-compiler, bump@vitejs/plugin-react6.0.4 → 6.1.0, and addoxc-transform-react@0.145.0. - Toast defaults —
ToastActionandToastClosemove from a destructuring defaultrender = <Button …/>torender ?? <Button …/>inside the JSX. - Expo untouched —
apps/mobilekeepsbabel-plugin-react-compilerandapp.config.jsreactCompiler: true, which is correct since Oxc's path is Vite-only. - Lockfile — the
oxc-transform-reactbinding 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 | ReactCompilerOptionsexists in the installed 6.1.0 typings, and the pluginawait import("oxc-transform-react")s dynamically and hard-errors when it is absent — so the directdevDependencyin both workspaces is required, not decorative.- Fast Refresh is not lost.
vite:react-babelsetsrefresh: command === "serve" && !opts.compiler, and the compiler plugin instead threadsjsx.refreshinto 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\//) anddefaultCodeFilter, andpackages/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 reorderedand leaves the component uncompiled, while the??form compiles and emitsuseMemoCache.message-scroller.tsx:105already 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/srcandpackages/ui/srcproduced zero remaining diagnostics — the migration is complete, not partial.bun run codegen+bun run build --filter=apppass clean and the emitted client bundle containscompiler-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.lockcarries collateral changes beyond the intended dependency edits —@react-navigation/{core,native,routers}, the top-levelexpo-file-system19.0.23→57.0.1hoist,files-sdk/ai@6.0.27, and the two@standard-community/*/effectsubtrees. I traced each: all four are unreferenced optional peer resolutions being pruned or deduped,apps/mobilealready resolvedexpo-file-system@57.0.1throughexpo@57.0.10, andposthog-react-native@4.17.3ships 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 neitherapps/mobilenorapps/apiwas built as part of verification.
Claude Opus | 𝕏

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=appbun run build --filter=desktopbun testbun run analyzebun run check:monorepobun run checkpasses in the active repository workspace. A clean worktree check still reports the existing Astro virtual-type resolution errors inapps/docsandapps/web; both changed production builds pass from that clean worktree.Made with GPT-5.6 in the T3 Code Codex harness.