fix(compiler,vxrn): run codegen on React Native's Flow .js specs#735
Merged
Conversation
RN's own component specs are Flow .js (e.g. RCTSafeAreaViewNativeComponent.js). @react-native/babel-plugin-codegen must replace codegenNativeComponent<Props>(...) with a static view-config at build time, but it never runs on them — so the runtime codegenNativeComponent executes and RN logs "Codegen didn't run for SafeAreaView. This will be an error in the future." on every render/hot reload. Two layers, both about Flow .js never reaching codegen with its type intact: 1. @vxrn/compiler's transformBabel adds a parser only for TS (.ts/.tsx via preset-typescript) and nothing for .js, so babel can't parse RN's Flow specs (import type, codegenNativeComponent<T>(...), casts). It throws, transformBabel returns undefined, and the file falls back to the SWC path (strips Flow, no codegen). Fix: parse Flow for non-TS files by appending @babel/plugin-transform-flow-strip-types (parses + strips), mirroring @react-native/babel-preset (TS -> preset-typescript, JS -> this plugin). The TS path is byte-identical. 2. On the native bundler, flowStripPlugin runs before the compiler and strips Flow from RN files, erasing the type argument codegen needs. Fix: in flowStripPlugin, when a file uses codegenNativeComponent, run @react-native/babel-plugin-codegen with a Flow parser first (while the type is present), then strip Flow. Graceful: any error falls through to the plain strip, never a broken build. Adds @babel/plugin-transform-flow-strip-types to @vxrn/compiler's deps (was only transitive) and @react-native/babel-plugin-codegen to vxrn's deps (now referenced by flowStripPlugin).
…rop the duplicate Running @react-native/babel-plugin-codegen inside flowStripPlugin was a layering violation — codegen ran in two packages with two divergent gates plus a silent catch. Instead, run vxrnCompilerPlugin BEFORE flowStripPlugin: now that the compiler parses Flow, react-native's Flow .js specs reach the codegen the compiler already owns with their type argument intact, and flowStripPlugin strips any remaining Flow downstream as the guaranteed safety net before oxc's core parse. Removes the flowStripPlugin codegen block, its silent catch, and vxrn's now-unused @react-native/babel-plugin-codegen dependency.
YevheniiKotyrlo
marked this pull request as draft
July 15, 2026 17:35
natew
marked this pull request as ready for review
July 16, 2026 00:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a native dev build (new architecture / bridgeless), React Native logs a LogBox warning on every render + hot reload:
Non-fatal today, but RN has slated it to become a hard error.
SafeAreaViewis RN core's own deprecated Flow spec (RCTSafeAreaViewNativeComponent.js) — itscodegenNativeComponent<Props>(…)call is never replaced with a static view-config at build time, so the runtimecodegenNativeComponentexecutes and warns.Root cause
Two layers, both about Flow
.jsnever reaching codegen with its type intact:1.
@vxrn/compilerhas no Flow parser for.js.transformBabeladds@babel/preset-typescriptonly for.ts/.tsxand nothing for.js:RN's own component specs are Flow
.js(import type,codegenNativeComponent<T>(…),(expr: Type)casts).@react-native/babel-plugin-codegendeclares nomanipulateOptions/inherits, so it doesn't enable Flow parsing itself — its visitor needs babel to have already parsed theTypeCastExpression/CallExpression. With no Flow syntax plugin, babel's parser throws on the first line (Unexpected token … import type);transformBabelonly silences"Could not find component config", so it logs and returnsundefined, and the file falls back to the SWC path (which strips Flow but does not run codegen). The rawcodegenNativeComponent("SafeAreaView", …)survives into the bundle → the runtime warning. (safe-area-context's own spec is a.tsfile, so preset-typescript parses it and codegen runs — which is whyRNCSafeAreaViewworks butSafeAreaViewdoesn't.)2. On the native bundler, Flow is stripped before codegen can run.
getNativePluginsrunsflowStripPluginbefore the compiler. It matches RN files (node_modules/(react-native|@react-native)/**/*.js) and strips Flow viafast-flow-transform, socodegenNativeComponent<Props>(…)becomescodegenNativeComponent(…)(type erased) before the (now Flow-capable) compiler — which already owns codegen — ever sees it. So fixing (1) alone isn't enough on native: the type argument is already gone.Fix
Two coordinated changes that keep codegen a single responsibility of
@vxrn/compiler.@vxrn/compiler— parse Flow for non-TS files. Append@babel/plugin-transform-flow-strip-types(whichinherits@babel/plugin-syntax-flow, so it both parses and strips) to the plugin list for non-TS files, mirroring@react-native/babel-preset(TS →preset-typescript, JS → this plugin). The general fix — any Flow.jsthrough babel now parses, so the codegen plugin the compiler already runs works on RN specs. The TS path is byte-identical (empty slice).vxrn— run the compiler beforeflowStripPlugin.getNativePluginsnow ordersvxrnCompilerPluginahead offlowStripPlugin, so RN's Flow.jsspecs reach the compiler's codegen with their type argument intact;flowStripPluginthen strips any remaining Flow downstream as the guaranteed safety net before rolldown's oxc core parse. Because the safety net now sits after the transform instead of before it, it can't leave un-stripped Flow for oxc to choke on (the failure mode of the naive "skip specs in flowStrip" alternative).Dependency:
@babel/plugin-transform-flow-strip-typeswas only present transitively — added to@vxrn/compiler's deps.Test plan
getBabelOptions+transformBabel): the RN core Flow.jsspecRCTSafeAreaViewNativeComponent.js— before,transformBabelreturnsundefined(SyntaxError onimport type) and codegen does not run; after, codegen runs, replacingcodegenNativeComponent('SafeAreaView', …)withNativeComponentRegistry.get('RCTSafeAreaView', () => __INTERNAL_VIEW_CONFIG). A.tsspec is unchanged (no regression); the TS path is byte-identical.codegenNativeComponent("SafeAreaView")count goes 1 → 0; theCodegen didn't runwarning is gone (initial render + hot reload); app renders + hot-reloads; build healthy. The pipeline reorder is behavior-changing, so an on-device re-smoke is worthwhile.@vxrn/compilerhas no test harness today and this entry pulls unbuilt workspace deps, so testing it would need new infrastructure plus a build-before-test step CI doesn't run — out of scope here; the behavior is covered on-device.Notes
@react-native/babel-plugin-codegenin two places (the compiler and the strip plugin), the compiler owns it and the pipeline order lets it see the Flow type. This drops a second, divergent codegen gate and a silentcatch {}that would otherwise have hidden a future codegen regression behind the exact warning this fixes.transformBabelowns how to parse a file (dialect by extension: TS vs Flow); the pipeline owns when each transform runs. The bug was that the parse layer only knew TypeScript and the strip ran before the transform — this fixes both.