fix(vxrn): include transform-classes in native dev bundles (Hermes)#733
Merged
natew merged 2 commits intoJul 16, 2026
Merged
Conversation
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.
…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.
YevheniiKotyrlo
marked this pull request as draft
July 15, 2026 17:17
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
With
native.bundler: 'vite', a dev native build compiles, installs, and launches, then Hermes red-screens at init, before React mounts:The app never renders. Production (
one build android) is unaffected. The same JS runs clean under Node/JSC — it is Hermes-specific.Root cause
vxrn downlevels the dev bundle for Hermes with SWC, but gates
transform-classesbehind!devin two places (packages/vxrn/src/utils/createNativeDevEngine.ts), so dev builds transpile class fields while leavingclass extendsas modern ES6 — an inconsistent, half-transpiled class hierarchy Hermes chokes on when constructing the class:downlevelClassFieldsInBundle(the injected runtime prelude) —includehadtransform-class-properties/-static-block/-private-*but notransform-classes.hermesCompatSWCPlugin(app modules) —...(!dev ? ['transform-classes', 'transform-async-to-generator'] : []), i.e.transform-classesonly in production.Bisected on device: adding
transform-classesto (1) advanced the crash from vxrn's own runtime into RN's modules (BatchedBridge/MessageQueue); adding it to (2) cleared those too — pinpointing the!devgate as the whole cause.Fix
Make
transform-classesunconditional in both spots so the entire native dev bundle is consistently ES5-classed and Hermes-safe (matching the fully-classed production bundle);transform-async-to-generatorstays production-only.The two include lists were hand-copied — which is exactly how
transform-classescame to be missing from one of them — so this also extracts the shared set into a single named constant (HERMES_CLASS_TRANSFORMS) plus the prod-only addend (HERMES_PROD_TRANSFORMS), both consumed throughgetHermesSWCIncludes(dev). The "these class transforms move together" invariant is now a compile-time fact instead of two lists that can silently drift apart again.Test plan
createNativeDevEngine.test.ts):getHermesSWCIncludesalways contains the full class-transform set for both dev and prod (the regression guard —transform-classeswas the missing member) and addstransform-async-to-generatoronly in prod.include(withouttransform-classes) keepsclass X extends Yas ES6 while lowering the fields (half-transpile); addingtransform-classesemits full ES5 (_inherits/_call_super/_class_call_check).class) output is rejected by the Hermes compiler (invalid statement encountered), while the fully-lowered ES5 output compiles to valid bytecode.prototype of undefinedatnew ReactNativeDevRuntime()) → renders;e2e-check.shnative-renderFAIL→PASS. Durable across the whole session.Notes
Not Windows-specific — this is a Hermes + dev-mode issue; any
bundler: 'vite'native dev target hits it. The!devgate reads like a dev-speed optimization (skip the heavier class transform in dev), but a half-transpiled class hierarchy is not Hermes-safe, so keepingtransform-classesunconditional is the correct trade-off.