Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-native support #123

Open
matinzd opened this issue Mar 9, 2022 · 27 comments
Open

react-native support #123

matinzd opened this issue Mar 9, 2022 · 27 comments
Labels
tracking An umbrella issue for tracking big features

Comments

@matinzd
Copy link

matinzd commented Mar 9, 2022

Hi
I know this project is in alpha and we are at the start of the road. Right now I tried to make bun work with react-native (Hermes) but I got the following error. Hope to have react-native support in the future and consider it.

error: node_modules/@react-native-community/cli-plugin-metro/node_modules/metro/src/node-haste/DependencyGraph/assets/empty-module.js: Transformer is not a constructor
@Jarred-Sumner
Copy link
Collaborator

Jarred-Sumner commented Mar 10, 2022

I'd love if bun worked with React Native

Regardless of bugs with bun today, support for Hermes is harder

Bun doesn't currently transpile to ES6 (or backwards transpilation at all, yet), and Hermes lacks native support for many JavaScript features, like classes

It is unlikely Bun will support transpiling classes (for similar reasons as esbuild)

Bun uses JavaScriptCore as it's engine, I do think it would be pretty interesting to get bun's JavaScript environment to run as the engine. That would mean adding support for JSI and adding Android + iOS targets & architectures. This won't happen soon, but I think that would be an interesting way to make it work

Regarding this particular problem:

error: node_modules/@react-native-community/cli-plugin-metro/node_modules/metro/src/node-haste/DependencyGraph/assets/empty-module.js: Transformer is not a constructor

It would be tough to use bun as just the transpiler today, and much easier if used as both the bundler & transpiler

For that to possibly work, it would probably not be using @react-native-community/cli-plugin-metro. It would probably need an entry point that is not yet transpiled which loads up the app's entry point and runs any react native initialization code

@iamyellow
Copy link

kudos for your work @Jarred-Sumner, it' insane!
This would be awesome. I'm thinking seriously to play with this idea, as we can use JSCore both for iOS and Android, I think it's doable since there's no need to use jsi (I did it before jsi was a thing just to avoid the bridge). I mean, we can go straight to the JSCore env.
Just the idea of having n-api implementation on mobile opens up a lot of doors!

@vjpr
Copy link
Contributor

vjpr commented Jul 20, 2022

JS Engine

React Native used to use JavaScriptCore (which Bun uses), but they wrote Hermes for perf reasons on mobile. It wouldn't make sense to go back to JSC now. And Hermes is recently the default engine for all RN apps.

Bundler

The Metro bundler though is a PITA and would be great to get rid of, and use Bun instead. There is an alternative Webpack-based bundler for React Native called repack which would be a good starting point.

Transpiler

As mentioned above, Hermes doesn't support classes yet, and esbuild (used by Bun), doesn't support transpiling to ES5.

If Bun could use SWC for its transpiler, then it would be possible.

I also wonder if Bun considered Hermes instead of JSC. It's optimized for fast startup times.

@evelant
Copy link

evelant commented Sep 22, 2022

It seems the hermes runtime is too immature to be a target for much other than Facebook's metro with it's specific configuration. It does improve TTI but appears to be slower and less efficient in all other regards. It also currently implements pretty wild unsafe behavior such as treating let and const as var (incredibly IMO.... why?!?) which would certainly introduce bugs and memory leaks unless you specifically transpiled to safe var usage first.

In my react-native app I've opted out of Hermes in favor of keeping JSC due to runtime performance and stability issues with Hermes. Switching between the two is as simple as a single flag, it's transparent.

From what I've gathered alternative runtimes are not going away in react-native. It seems going forward that react-native will be largely runtime agnostic and support any runtime that supports JSI. You can currently switch between JSC, V8, or Hermes more or less seamlessly in a react-native app. V8 on iOS is a bit more complex since it has to be compiled without JIT -- IIRC Apple's rescrictions don't disallow JS engines outside of JSC but rather they disallow any sort of JIT compilation outside of JSC.

With that, it would probably be possible and perhaps even easy to drop Bun into react-native as a replacement engine since react-native already runs on and will continue to run on JSC in the future. I don't have the time to try it myself right now but I think Bun definitely has a ton of potential for improving the (generally awful) react-native DX.

@evelant
Copy link

evelant commented Feb 3, 2023

If anybody with more cpp experience than me (I have zero) wants to look into this here is react-native's implementation of a JSC runtime for react-native. https://github.com/facebook/react-native/blob/main/ReactCommon/jsc/JSCRuntime.cpp

@ozyman42
Copy link

ozyman42 commented Feb 7, 2023

Lately I've decided to give up on React Native and am opting for Tauri mobile instead. Posting it here for others who might be fed up w/ the difficulty of integrating RN with the evolving JS ecosystem.

@keeslinp
Copy link

keeslinp commented Sep 8, 2023

I notice that this issue turned pretty quick from supporting metro to using bun as the runtime for react native applications. As a react native developer I'm much more interested in replacing node with bun than I am replacing hermes.

@evelant
Copy link

evelant commented Sep 8, 2023

I'm more interested in the opposite 😄 hermes has numerous severe problems that plague us constantly. Lack of block scoping facebook/hermes#575 (comment), an object property limit that prevents processing source maps facebook/hermes#851, 800-3200x worse date perf facebook/hermes#930, poor proxy perf, among other things. A more stable, feature complete, and performant runtime would be amazing!

@keeslinp
Copy link

keeslinp commented Sep 8, 2023

That's a fair take for sure. I just wanted to point out that there are very different usages of bun in a react native project with different levels of difficulty. Even if I could just replace pnpm and jest with bun that would be a huge win for our team if the perf benefits reflect benchmarks.

@natew
Copy link

natew commented Sep 8, 2023

@evelant RN runs on JSC already, at least if you turn of hermes, which is the same engine that runs bun. Bun makes sense as a package manager and maybe eventually bundler for RN but not really as the runtime.

@natew
Copy link

natew commented Sep 8, 2023

If you are interested in potentially better DX, I've gotten Vite to serve RN and web at the same time.

https://twitter.com/natebirdman/status/1698188508329050318

Working on getting it cleaned up for an initial release.

@melroy89
Copy link

I would love to see some kind of Hermes kinda implementation in Bun, this will parse & compile the JS / TS code, optimize during the build time. Making start-up faster and reduce memory consumption.

Basically the code will be precompiled to bytecode.

@EvanBacon
Copy link

Here are some of my thoughts on adding React Native support to Bun (or any bundler).

React Native resolution

A Metro replacement needs to be capable of the following:

  • Resolving platform extensions like .native.tsx, .ios.js, etc.
  • Resolving package.json fields with platform extensions, e.g. "main": "./index" should be able to resolve to ./index.ios.js when bundling for ios.
  • Resolving the react-native field in the package.json. Ideally this rule would be flexible, in Expo CLI we have a web target with a isServer variation that switches main field rules dynamically.

Community transpilation

Metro transpiles all files using the project babel.config.js, as a result, there are years of libraries that are shipped untranspiled with lots of different language features.

Transpiling React Native

One of the hardest parts of supporting React Native projects, is transpiling the react-native package. The React Native team has been unwilling to ship any source transpiled, and the release pipeline has a fair amount of native prerequisites. This means a bundler needs to generally be capable of stripping flow types, and resolving platform extensions inside modules, preserving lazy imports, preserving the ReactNative global, and stripping dead Platform conditions (even in development).

Additionally, the sourcemap format includes a custom x_facebook_sources field.

The global runtime of React Native is a bit of a mystery, adding global side-effects that run before react-native has proven very challenging. Metro also has a special getModulesRunBeforeMainModule setting which sorts certain global modules in React Native to ensure they run before application code.

In my opinion, all of these globals should be split out of the core React Native JS bundle and potentially instantiated natively to prevent the need for all this complexity in the bundling pipeline.

As of writing this, these appear to be all the globals (iOS + Hermes) that are instantiated on the global:

React Native iOS JS side-effects
__rctDeviceEventEmitter
originalConsole
__fbGenNativeModule
window
self
__SYSTRACE
setTimeout
clearTimeout
setInterval
clearInterval
requestAnimationFrame
cancelAnimationFrame
requestIdleCallback
cancelIdleCallback
setImmediate
clearImmediate
fetch
__fetchSegment
__REACT_DEVTOOLS_CONSOLE_FUNCTIONS__
__ReactRefresh
XMLHttpRequest
FormData
Headers
Request
Response
WebSocket
Blob
File
FileReader
URL
URLSearchParams
AbortController
AbortSignal
DOMRect
DOMRectReadOnly
alert
queueMicrotask
performance
navigator
Promise
console
ErrorUtils
React Native iOS native side-effects (Hermes)
__jsiExecutorDescription
nativeModuleProxy
nativeFlushQueueImmediate
nativeCallSyncHook
globalEvalWithSourceUrl
nativeLoggingHook
nativePerformanceNow
__blobCollectorProvider
__tickleJs
__fbBatchedBridge
Metro globals

Most of these come from the metro runtime React Refresh, and the __prelude__ (banner).

process
__BUNDLE_START_TIME__
__DEV__
__METRO_GLOBAL_PREFIX__
__requireCycleIgnorePatterns
__r
__d
__c
__registerSegment
$RefreshReg$
$RefreshSig$

So plan accordingly when creating any bundler-runtime code or attempting to simplify the bundling setup.

Multi-platform support

One often overlooked feature of Metro is the simultaneous multi-platform support. In the past, when I've tried to get other bundlers working for React Native this has been one the bigger blockers. As soon as you want to connect different platforms to the same server, you end up having to spin up multiple different bundler instances which bogs everything down. Being able to make all levels of the caching aware of different platforms is critical for keeping things snappy.

If bun could allow for providing abstract metadata like platform or isServer to the resolver and transformer, then it could unblock this. Even minor details like aliases often need platform-specific settings in place.

This will also be useful for React Server Components, being able to leverage the same graph and just modify transformation/resolution based on an output target.

Serving

The dev server needs to support serving .bundle with application/javascript content type. Metro also includes a /symbolicate endpoint for sourcemaps, the runtime-side of this is in react-native and not especially easy to change.

Unlike Webpack, Metro is lazy by default which is nice because it means you don't have to worry about multiple platforms bundling on start-up.

Metro used to have a bunch of middleware required for debugging, but the Expo team has removed a bunch of this in Expo SDK 49 so debugging shouldn't need to be tied to the bundler implementation.

Assets

Assets are matched against a glob of extensions and registered using React Native, images are measured during bundling. All assets support multi-resolution asset extensions, e.g. @2x, @3x, etc. see more here.

Worth noting that JSON can be treated as both an asset and a source file.

Expo Router

To support the basics of Expo Router, we'd need the ability to import directories, e.g. import.meta.glob or require.context. The majority of features I've added are pretty standard (CSS modules, server rendering) and either already exist or would be simple to add.

Freebies

There's currently no bundle splitting or tree-shaking in Metro (and no official plans to support it) so there's no caveats there to account for. We'll eventually build multi-bundle support into the Expo OTA service, but in the meantime, you'll need a system for outputting a single chunk on native.

Async chunk loading in Metro is pretty straightforward simply provide a global that can fetch and evaluate a bundle. One nice feature is that the dependency map (representing import ID to global module ID) is provided to the module, and configurable in the serializer. This means you don't need to have some external DLL manifest to map remote chunk URIs to module IDs.

A good (but outdated) reference is this branch which gets the basics working with ESBuild.

This list isn't exhaustive, but I hope it helps shed some light on the issues.

@natew
Copy link

natew commented Sep 20, 2023

@EvanBacon great write up. For the vite react native implementation I have I just made a prebuilt react native package that gets aliased, you could build out a bunch of them for each version of react and it’s only a small js script to combine esbuild and remove flow types. Was a lot easier than trying to integrate flow into bundler everywhere.

@EvanBacon
Copy link

@natew one issue with rolling up react-native is that many packages import internals. A quick modification of the Metro resolver to log react-native internals imports in an Expo Router app on iOS shows the following entry points are required:

react-native/Libraries/Core/InitializeCore.js
react-native/Libraries/Image/AssetRegistry.js
react-native/Libraries/Utilities/LoadingView.ios.js
react-native/Libraries/Utilities/HMRClient.js
react-native/Libraries/Core/Devtools/getDevServer.js
react-native/Libraries/Image/resolveAssetSource.js
react-native/Libraries/Image/AssetSourceResolver.js
react-native/Libraries/Network/RCTNetworking.ios.js
react-native/Libraries/Core/Devtools/openFileInEditor.js
react-native/package.json
react-native/Libraries/ReactNative/AppContainer.js
react-native/Libraries/Utilities/codegenNativeComponent.js
react-native/Libraries/Utilities/codegenNativeCommands.js
react-native/Libraries/Components/View/ReactNativeStyleAttributes.js
react-native/Libraries/Core/Devtools/symbolicateStackTrace.js

The Android equivalent (uniques only):

react-native/Libraries/Utilities/LoadingView.android.js
react-native/Libraries/Network/RCTNetworking.android.js

So the fork would need to have a fair amount of entry files left in place.

@natew
Copy link

natew commented Sep 21, 2023

Yea, had to do a custom transform where it exposes all the internal paths and forces all their exports to not be shaken out by rollup, now that I think about it that part does have a bit of complexity beyond just esbuild and flow.

@esummers
Copy link

I'd love to see this, but doesn't Bun require JIT support which isn't available on iOS?

@keeslinp
Copy link

I'd love to see this, but doesn't Bun require JIT support which isn't available on iOS?

I always thought jsc was the exception to that rule (well, who knows after eu gatekeeper stuff), so in theory bun is fine? But I've also heard it's only out of process jsc that is okay so maybe not viable.

@esummers
Copy link

esummers commented Sep 28, 2023

I'd love to see this, but doesn't Bun require JIT support which isn't available on iOS?

I always thought jsc was the exception to that rule (well, who knows after eu gatekeeper stuff), so in theory bun is fine? But I've also heard it's only out of process jsc that is okay so maybe not viable.

Unfortunately not. iOS will only allow JIT in a WKWebView that runs in another process so you can take advantage a little bit for hybrid apps. No way to access JSC in that process and there is some latency from interprocess communication through the provided SDK. I like WKWebView for document editing since content editable is superior these days to the buggy TextKit 2. It is nice to have that accelerated with JIT. ProseMirror and CodeMirror are very nice on iOS.

It wouldn't work in general for React Native though. No JIT or WASM in JSC. These days most React native is more about AoT bytecode generation for faster startup times. Even on Android where JIT is allowed, React Native just interprets.

@Electroid Electroid added the tracking An umbrella issue for tracking big features label Oct 13, 2023
@RayyanNafees
Copy link

RayyanNafees commented Nov 3, 2023

Seems like someone had success using bun with Expo

image

@aashishsingla567
Copy link

Seems like someone had success using bun with Expo

image

Which version of bun ?

@headfire94
Copy link

Seems like someone had success using bun with Expo

As i understand this is only related to package management (installing node_modules) and initializing the project itself, a bit out of scope here, though nice improvement

@stevehanson
Copy link

Interestingly, this all worked for me:

 bunx create-expo@latest MyBunApp
cd MyBunApp
bun ios

This ran the app in the simulator with Metro running in the background. bun expo prebuild --clean also worked for me.

@aashishsingla567
Copy link

Interestingly, this all worked for me:

 bunx create-expo@latest MyBunApp
cd MyBunApp
bun ios

This ran the app in the simulator with Metro running in the background. bun expo prebuild --clean also worked for me.

But is there any benefit of running these commands via bun as opposed to any other package manager like yarn or npm ?
As they seem to be similarly performant and not using bun after running.

@esummers
Copy link

This ran the app in the simulator

Note that the iOS simulator supports JIT in JSC, but the actual iOS device does not.

@ko-devHong
Copy link

ko-devHong commented Mar 6, 2024

@EvanBacon My app is integrated into the existing app. I'm purely creating a bundler and combining the react-native with the existing app. How can bun be used in this state?

NOTE : this is not expo

@Nanome203
Copy link
Contributor

Any updates on this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tracking An umbrella issue for tracking big features
Projects
None yet
Development

No branches or pull requests