Skip to content

fix(napi): make the iOS and Android Node-API engine packages boot a Vite-bundled app - #74

Open
NathanWalker wants to merge 4 commits into
android-react-nativefrom
fix/napi-engines-esm-and-metagen-rpath
Open

fix(napi): make the iOS and Android Node-API engine packages boot a Vite-bundled app#74
NathanWalker wants to merge 4 commits into
android-react-nativefrom
fix/napi-engines-esm-and-metagen-rpath

Conversation

@NathanWalker

Copy link
Copy Markdown
Contributor

iOS

  • Metadata generator: the binary links @rpath/libclang.dylib and only
    carried the rpath of the CI machine's Xcode, so it failed with "Library not
    loaded" wherever Xcode is installed under another name. It now also carries
    the stock Xcode.app and Command Line Tools rpaths, and the build-step driver
    hands dyld the active toolchain through DYLD_FALLBACK_LIBRARY_PATH.
  • Simulator link: the xcframework ships an arm64-only simulator slice while
    the CLI builds simulators with ONLY_ACTIVE_ARCH=NO. The template xcconfig now
    keeps x86_64 out of iphonesimulator builds.
  • Hermes framework: ios-hermes linked a dynamic hermes.framework the package
    never shipped, so the app died in dyld. The hermes variant now ships
    internal/hermes.xcframework and the pack step adds it to the template's
    Embed Frameworks phase.
  • Extended classes: extendNativeApiClass resolved a freshly registered
    class's JS value by name through globals. core publishes
    global.SceneDelegate, and a TS constructor inherits kind/runtimeName from
    its base wrapper, so the lookup returned UIResponder ("delegateClass must
    conform to UISceneDelegate"). The value is now created from the class
    pointer only.

Android

  • ES modules: the loader rejected .mjs, Module.java only probed .js,
    and the ES-module-to-CommonJS rewrite lived only in the Apple loader. The
    rewrite is now a shared unit (runtime/modules/esm/ESModuleSupport) used by
    every Android engine. It binds generated code to captured aliases instead of
    the wrapper's exports/module/require (bundler output declares its own
    top-level module and exports), rewrites import.meta, and scans
    import/export lines instead of running std::regex over whole files, which
    took seconds on the 2.4 MB vendor chunk and ANR'd the app. .mjs and
    .cjs resolve in the Apple runtime's order.
  • Microtasks: Hermes, QuickJS and PrimJS only drain their job queue on
    request, so the bundle's lazy import of the Activity chunk never ran and the
    Activity was instantiated before its JS extension existed ("Failed to create
    JavaScript extend wrapper for class com/tns/NativeScriptActivity"). The
    runtime now runs a microtask checkpoint when the outermost Java-to-JS call
    returns, after worker callbacks, and after the worker entry script. The
    Hermes drain converts a throwing job into a pending exception instead of
    unwinding through JNI.
  • Early errors: exceptions raised before core installs __onUncaughtError
    are logged to logcat instead of being dropped.
  • Local builds: -PonlyArm64 is forwarded so a runtime build can target a
    single ABI.

Known limitation

HMR mode is still unsupported on the Node-API runtimes. Neither platform has an
HTTP module loader for dev-server imports, and the Android static binding
generator does not follow the ns-vendor.mjs stub's bare import. --no-hmr
bundles are self-contained and work.

… layout

The generator links @rpath/libclang.dylib and only carried the rpath of the
toolchain that linked it, so a published package failed with "Library not
loaded" on any machine whose Xcode is installed under another name. The
binary now also carries the stock Xcode.app and Command Line Tools rpaths,
and the build-step driver hands dyld the active toolchain through
DYLD_FALLBACK_LIBRARY_PATH for every remaining layout.
…m an app

Two gaps between the packaged runtime and what an app build needs:

- The xcframework ships an arm64-only simulator slice, but the CLI builds
  simulator targets with ONLY_ACTIVE_ARCH=NO, so the x86_64 link failed.
  The template xcconfig now keeps x86_64 out of simulator builds.
- ios-hermes links a dynamic hermes.framework that the package never
  shipped, so the app died in dyld. The hermes variant now ships
  internal/hermes.xcframework and the pack step adds it to the template's
  Embed Frameworks phase.
…ava calls

Vite emits the app as .mjs chunks with a lazily imported Activity chunk.
The Android Node-API runtime could not load them: the loader rejected the
extension, the Java resolver only probed .js, and promise reactions never
ran after the entry module because Hermes, QuickJS and PrimJS only drain
their job queue on request. The Activity was then instantiated before its
JS extension existed.

- Extract the Apple loader's ES-module-to-CommonJS rewrite into a unit
  shared by both platforms and use it for every Android engine. It binds
  generated code to captured aliases rather than the wrapper parameters
  (bundler output declares its own top-level module/exports), rewrites
  import.meta, and scans instead of running std::regex over whole files
  (the vendor chunk took seconds and ANR'd the app).
- Resolve .mjs and .cjs in Module.java, in the Apple runtime's order.
- Drain the job queue when the outermost call from Java into JS returns,
  after worker callbacks, and after the worker entry script; the Hermes
  drain now converts a throwing job into a pending exception instead of
  unwinding through JNI.
- Report errors that arrive before core installs its handler to logcat
  instead of dropping them.
- Forward the onlyArm64 property so a local runtime build can target one ABI.
… globals

extendNativeApiClass created the JS value for a freshly registered class via
makeNativeClassValue, which falls back to a by-name global lookup. core
publishes its delegate constructors on global (global.SceneDelegate) and a
TypeScript constructor inherits kind/runtimeName from its base wrapper, so
the lookup returned the base class: the scene delegate materialized as
UIResponder and UIKit rejected it ("delegateClass must conform to
UISceneDelegate"). The new class value is now created by pointer only.
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: a74f442f-40eb-433f-b538-b651d60abe6d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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