Skip to content

fix: integrate reviewed build and native development fixes#737

Merged
natew merged 20 commits into
mainfrom
integrate/yevhenii-prs-731-736
Jul 16, 2026
Merged

fix: integrate reviewed build and native development fixes#737
natew merged 20 commits into
mainfrom
integrate/yevhenii-prs-731-736

Conversation

@natew

@natew natew commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Integrates the reviewed work from #731, #732, #733, #734, #735, and #736 on one branch, with conflicts resolved and combined-stack cleanup.

Additional fixes from integration review:

  • scope the React Native HMRClient alias so app-authored modules are untouched
  • preserve React Refresh registrations in initial Rolldown native bundles
  • enable the Hermes Flow parser before React Native codegen
  • add unit coverage and an automated iOS route/component Fast Refresh regression

Validation:

  • full bun run ci: 23/23 builds, 4/4 checks, 37/37 typecheck/lint tasks, 30/30 test tasks; main suites passed in dev, prod, and non-CLI dev modes
  • iOS 18.6 Rolldown dev smoke: component edit refreshed without route remount; route edit refreshed with one route remount
  • native logs contained no HMR connection/eval error, unknown-message LogBox, or codegen warning

Please merge this PR with a merge commit so the original PR heads become ancestors of main.

YevheniiKotyrlo and others added 20 commits July 15, 2026 18:42
FSExtra.rm passes through to the raw callback-style Node fs.rm on fs-extra
< 9, so `await FSExtra.rm(staticDir, { force, recursive })` crashes on
teardown ("callback is not a function", thrown async -> uncaught ->
process.exit(1)) when a consumer's tree hoists an older fs-extra above
one's nested 11.x. FSExtra.remove is promise-safe on every fs-extra
version and is already this file's own idiom.
REANIMATED_IGNORED_PATHS_REGEX was built with `s.replace(/\//g, '/')`, a
no-op, so it only matched forward slashes. On Windows the module id uses
backslashes, so react-native's own Animated files were not ignored and got
pushed through the isolated reanimated babel pass (no JSX syntax), throwing
"Support for the experimental syntax 'jsx' isn't currently enabled". Accept
either separator (`[/\]`), mirroring SPEC_FILE_RE in the same file.
transform-classes was gated behind !dev in two spots, so dev native
bundles transpiled class fields but left `class extends` as modern ES6.
Hermes chokes on that half-transpiled class hierarchy at `new Subclass()`
(TypeError: Cannot read property 'prototype' of undefined), red-screening
the app at init on the Vite-native path. Make transform-classes
unconditional (matching the fully-classed production bundle); keep only
async-to-generator production-only.
On the new architecture, RN registers its real HMRClient via
registerCallableModule during setUpBatchedBridge — before vxrn's
bundle-splice stub runs — and the underlying std::unordered_map::emplace
keeps the first registration. So RN's HMRClient.setup() survives, opens a
MetroHMRClient to /hot, and red-boxes "unknown-message [object Object]" on
every edit (metro's client can't parse vxrn's hmr:* frames).

Alias react-native's Utilities/HMRClient to a no-op module at resolve time
so RN registers the no-op as the first-and-only HMRClient — working with
emplace, arch-agnostically. MetroHMRClient is never constructed (and
tree-shakes out of the bundle), so the socket never opens and the error
cannot occur. vxrn's own HMR (the rolldown-runtime WebSocket) is untouched.

Also removes two now-dead workarounds that only chased the symptom: the
emplace-losing bundle-splice stub, and a global ErrorUtils handler that
matched 'HMRClient' (the thrown text is "unknown-message [object Object]",
so it never caught this error).
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).
On the Vite-native dev path, editing a source file never updated the running
app. React Refresh runs on device but can't repaint One's route components:
useScreens re-wraps loadRoute()'s export (fromImport -> forwardRef -> getPageExport),
so the mounted fiber isn't in the edited module's Refresh family. One ships a
route-cache-refresh fallback, but on native three links were missing:

1. vxrn's rolldown runtime commits each HMR patch in applyUpdates() but never
   surfaced the updated module ids to a framework hook (the web one:route-update
   event has no equivalent on the native /hot transport).
2. ScreenComponent never subscribed to a route-hot signal, so nothing re-ran
   loadRoute().
3. hmrImport.native was a rejecting stub, so resolve() fell back to the memoized
   original module.

Fix the whole chain:
- vxrn applyUpdates surfaces each committed boundary id to
  globalThis.__oneRouteHotUpdate (guarded; a no-op if nothing registered it).
- useScreens registers that hook (evict the route cache via
  window.__oneRouteCache.clearFile, which bumps hmrVersion so resolve() re-imports,
  + bump a subscribable epoch) and subscribes ScreenComponent via
  useSyncExternalStore so it re-renders and re-runs loadRoute().
- hmrImport.native re-imports from the live rolldown runtime
  (__rolldown_runtime__.loadExports), replacing the rejecting stub. No dynamic
  import() (Hermes can't parse it); falls back to reject when the runtime is absent.

All one-side changes are gated to TAMAGUI_TARGET === 'native', so the web path
(one-hmr-update) is untouched.
Cover resolveId (aliases RN Utilities/HMRClient across both separators and
optional js/ts/cjs extension; leaves near-misses like HMRClientRegistry alone)
and load (no-op module exposing every method RN calls). Export the plugin
factory so the test drives it directly.
…t drift

The Hermes SWC downlevel include list was hand-copied in two call sites, and the
original bug was transform-classes missing from one of them. Define the class set
once (HERMES_CLASS_TRANSFORMS) plus the prod-only addend (HERMES_PROD_TRANSFORMS),
consumed by both sites via getHermesSWCIncludes(dev). Behavior-identical; makes the
"these move together" invariant a compile-time fact rather than a review-time hope.
Unit-tested.
…ch on Windows

The reanimated ignore-list (and the native optimizeDeps filter in index.ts) assumed
forward-slash ids, but the native/patches path hands OS-native ids — so on Windows
react-native's own files slipped past the ignore list into the reanimated babel pass
(which has no JSX/TS parser), and optimizeDeps' node_modules filter dropped every file.

Normalize the id once at getBabelOptions' entry (mirroring transformSWC.ts) so every
path matcher can assume forward slashes; the reanimated regex reverts to a plain join.
Fix the same-class filter in index.ts to accept either separator.
…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.
…date hook

Move One's native route-HMR store out of useScreens.tsx into a dedicated
routeHmr.native.ts (with a web no-op routeHmr.ts), matching the existing
hmrImport.ts/.native.ts platform split -- removes the import-time global side
effect from useScreens and makes the store unit-testable.

Replace the ad-hoc globalThis.__oneRouteHotUpdate with a typed vxrn extension
point, globalThis.__VXRN_ON_MODULE_UPDATED__, declared next to
__VXRN_CUSTOM_HMR_HANDLER__: vxrn's applyUpdates surfaces each committed module id
to it, and any framework (here One) registers a handler. Behavior unchanged. Adds
unit tests for hmrImport.native (loadExports) and the routeHmr.native store.
Agentbus-Session: ab-mrmnqe8m-76176
Agentbus-Session: ab-mrmnqe8m-76176
Agentbus-Session: ab-mrmnqe8m-76176
@railway-app
railway-app Bot temporarily deployed to onestack.dev / one-pr-737 July 16, 2026 00:58 Destroyed
@railway-app

railway-app Bot commented Jul 16, 2026

Copy link
Copy Markdown

🚅 Deployed to the one-pr-737 environment in onestack.dev

Service Status Web Updated (UTC)
one ✅ Success (View Logs) Web Jul 16, 2026 at 1:00 am

@natew
natew added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 16, 2026
@natew
natew added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 16, 2026
@natew
natew added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 16, 2026
@natew
natew merged commit 349f4f9 into main Jul 16, 2026
3 checks passed
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.

2 participants