Skip to content

Support React Native - #118

Open
hans00 wants to merge 203 commits into
huggingface:mainfrom
mybigday:merge
Open

Support React Native#118
hans00 wants to merge 203 commits into
huggingface:mainfrom
mybigday:merge

Conversation

@hans00

@hans00 hans00 commented May 19, 2023

Copy link
Copy Markdown
Contributor

Make it support React Native.

And I made a example for it.
https://github.com/hans00/react-native-transformers-example

Guide to get it start

TODO:

  • Check models are works fine
  • Research more efficiently image processing
  • Merge v3
  • Check everything are works fine on v3

@xenova

xenova commented May 19, 2023

Copy link
Copy Markdown
Collaborator

Woah nice! There are a lot of people who are interested in doing stuff like this! Looking forward to reviewing this when you're ready! cc @pcuenca

Comment thread package.json Outdated
@jhen0409

jhen0409 commented May 20, 2023

Copy link
Copy Markdown

I'm investigating a performance issue with Whisper tiny.en, and it looks like the performance is not as expected in the example.

I quickly add log for onnxruntime-react-native/lib/backend.ts, this is the result:

# Encode result
 LOG  ONNX session run finished 767 ms

# Decode result
 LOG  ONNX session run finished 4518 ms
# ... 4 runs ...
 LOG  ONNX session run finished 4856 ms
 LOG  Time: 40016
 LOG  Result: {"text": " (buzzing)"}
Click to expand
const t0 = performance.now()
// this.#inferenceSession === NativeModules.Onnxruntime
const results: Binding.ReturnType = await this.#inferenceSession.run(this.#key, input, outputNames, options);
const output = this.decodeReturnType(results);
console.log('ONNX session run finished', performance.now() - t0)

Also see logs of native part (added log to OnnxruntimeModule.java), it is significantly less than the time taken by JS part:

# Encode result
09:47:59.203 ONNXRUNTIME_RN run() is finished, time: 273 ms

# Decode result
09:48:00.280 ONNXRUNTIME_RN run() is finished, time: 339 ms
# ... 4 runs ...
09:48:23.807 ONNXRUNTIME_RN run() is finished, time: 541 ms

I think this problem may come from blocking caused by native module passing too large data. I think request/help onnxruntime for migrate to JSI module may solve this problem.

EDIT: It seems the logger have some bugs leads me to think that the problem is from the native bridge. I add timeout await to the decodeReturnType call and found the issue is from this function.

@axe-me

axe-me commented Jul 5, 2025

Copy link
Copy Markdown

@TowhidKashem same as the fugood one, merge branch.
Or you can just install it : npm i @axe-dev/transformers

@hans00

hans00 commented Jul 5, 2025

Copy link
Copy Markdown
Contributor Author

It seems it might have come from the native-universal-fs lib you're using, I tried replacing those instances with expo-file-system directly and using patch-package to update @fugood/transformers but the errors remain.
Any idea what could be the fix?

I had same issue in my recent trials...

This should fixed.
But currently uses webpack bundle to solve package alias, so that could not install from source.

@an-upfeat

Copy link
Copy Markdown

It seems it might have come from the native-universal-fs lib you're using, I tried replacing those instances with expo-file-system directly and using patch-package to update @fugood/transformers but the errors remain.
Any idea what could be the fix?

I had same issue in my recent trials...

This should fixed. But currently uses webpack bundle to solve package alias, so that could not install from source.

the 3.6.1-beta.1 release works for my app, thanks!

@rogueturnip

Copy link
Copy Markdown

Just curious, is this work integrated in the latest release (I see the PR is still open) or is this PR left hanging?

@bernardro

Copy link
Copy Markdown

@jhen0409 @kungfooman could one of you kindly merge this, sorry for the ping

@llop00

llop00 commented Oct 24, 2025

Copy link
Copy Markdown

@TowhidKashem I ran into same issue. I found it was because the metro reads the exports field of the package then loaded the browser version build. You can use my fork for now: https://www.npmjs.com/package/@axe-dev/transformers (I couldn't guarantee that I won't add my changes in there) or you can figure out some ways to load the build that bundled the universal fs package.

Does any of you could help on how they got to make it work on Expo? Im a newbie on mobile development and it seems to be impossible but saw you guys achieved it hahaha

@hans00

hans00 commented Oct 25, 2025

Copy link
Copy Markdown
Contributor Author

@TowhidKashem I ran into same issue. I found it was because the metro reads the exports field of the package then loaded the browser version build. You can use my fork for now: https://www.npmjs.com/package/@axe-dev/transformers (I couldn't guarantee that I won't add my changes in there) or you can figure out some ways to load the build that bundled the universal fs package.

Does any of you could help on how they got to make it work on Expo? Im a newbie on mobile development and it seems to be impossible but saw you guys achieved it hahaha

As far as I know, this tutorial may also works with Expo.
https://github.com/hans00/react-native-transformers-example/blob/main/DEVELOPMENT.md

And you need run local dev.

OpenClaw Subagent and others added 13 commits March 23, 2026 09:53
Conflict resolutions:
- package.json: take upstream version 4.1.0, merge RN deps
  (native-universal-fs, onnxruntime-react-native) with upstream's
  updated onnxruntime-web 1.26.0-dev
- audio.js: keep read_audio as primary (RN convention), add
  load_audio alias for upstream API compat; remove duplicate JSDoc
  and stale deprecated alias referencing non-existent load_audio

Also fixed:
- audio.js: add missing `apis` import from env.js (used in save())
- audio.js: remove unused `fs` import
- hub.js: use env.fetch consistently instead of bare fetch in RN path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Metro/RN bundler aliases path -> path-browserify at runtime;
direct import of path-browserify is not needed and breaks the
alias pattern. Use node:path (marked external in RN build,
Metro resolves it).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecated alias

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 4.0.0-next.8 upstream merge (04faed2) dropped several React Native
code paths, and 4.1.0-beta.0 shipped without them. In a real RN environment
(navigator.product === 'ReactNative', and no process.release, so
IS_NODE_ENV is false) loading any model threw:

  getCoreModelFile()                     -> "Cannot return path in a
                                            browser environment."
  ...with a progress_callback            -> "Cannot read properties of
                                            null (reading 'getReader')"

utils/model-loader.js asks getModelFile() for a path whenever
IS_NODE_ENV || IS_REACT_NATIVE_ENV, but the three matching checks in
utils/hub.js had been narrowed back to IS_NODE_ENV alone. They now share a
single CAN_RETURN_PATH constant so they cannot drift apart again.

Also fixed or restored:

  - readResponse() copes with a FileResponse that has no body stream. RN
    resolves file metadata up front and defers reading until the bytes are
    asked for, so the read happens there rather than eagerly in
    FileResponse.create().

  - Local paths are handed back as file:// URIs on RN, matching how
    native-universal-fs is addressed everywhere else.

  - package.json exports listed "react-native" after "default". "default"
    always matches, so the condition was unreachable and React Native
    resolved to the web/node bundle instead of transformers.native.mjs.

  - fetchBinary(): RN's fetch is text-oriented and corrupts binary
    payloads, so requests go through XMLHttpRequest with
    responseType='arraybuffer'.

  - downloadFile(): model weights stream straight to disk through
    native-universal-fs rather than being buffered in the JS heap, which
    exhausts it on device.

  - FileCache.put() read response.body on RN, but whatwg-fetch responses
    expose no body stream, so nothing was ever written to the cache --
    not even small config files.

None of the RN branches had test coverage: native-universal-fs was mapped
to node:fs/promises, which has no exists()/moveFile()/DocumentDirectoryPath,
so they could not run under test. It now maps to a mock backed by the real
filesystem, and tests/react_native.test.js exercises these paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- onnx.js: change RN defaultDevices from ['xnnpack', 'cpu'] to ['cpu']
  xnnpack implementation is incomplete; aligns with Node.js behavior
- env.js + FileCache.js: replace direct path-browserify import with
  node:path; Metro/RN bundler aliases path -> path-browserify at runtime
- audio.js: add missing load_audio alias (required by transformers.js exports)
- build/constants.mjs: add native-universal-fs, onnxruntime-react-native,
  buffer to web/node ignore lists; add buffer/native-universal-fs to RN
  external modules so Metro handles them
- ignoreModulesPlugin.mjs: add buffer case to export Buffer from globalThis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add aliasPlugin that rewrites node:path/path → path-browserify at build time,
so RN consumers don't need to configure Metro aliases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… cast, configs subfolder type, revision default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deviations from upstream should only exist where React Native needs them.
These three did not:

  - audio.js: RawAudio.save() had its own React Native branch writing through
    native-universal-fs, but io.js's saveBlob() -- the function it delegates to
    everywhere else -- already does exactly that. The duplicate pulled Buffer,
    NativeFS and apis into a module that otherwise needs none of them. Dropping
    it takes audio.js back to being byte-identical with upstream, RN included,
    and tests/react_native.test.js now pins saveBlob's native path so the
    duplicate does not come back.

  - FileResponse.js: inserting create() consumed clone()'s doc comment.

  - FileCache.js: match() had been restyled into a ternary; upstream's if/else
    is back, so the only difference left is the RN branch itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.