Open matinzd opened 2 years ago
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
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!
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.
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.
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
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.
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.
I'm more interested in the opposite 😄 hermes has numerous severe problems that plague us constantly. Lack of block scoping https://github.com/facebook/hermes/issues/575#issuecomment-1658812284, an object property limit that prevents processing source maps https://github.com/facebook/hermes/issues/851, 800-3200x worse date perf https://github.com/facebook/hermes/issues/930, poor proxy perf, among other things. A more stable, feature complete, and performant runtime would be amazing!
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.
@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.
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.
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.
Here are some of my thoughts on adding React Native support to Bun (or any bundler).
A Metro replacement needs to be capable of the following:
.native.tsx
, .ios.js
, etc.package.json
fields with platform extensions, e.g. "main": "./index"
should be able to resolve to ./index.ios.js
when bundling for ios
.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.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.
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:
So plan accordingly when creating any bundler-runtime code or attempting to simplify the bundling setup.
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.
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 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.
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.
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.
@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.
@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.
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.
I'd love to see this, but doesn't Bun require JIT support which isn't available on iOS?
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.
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.
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.
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.
This ran the app in the simulator
Note that the iOS simulator supports JIT in JSC, but the actual iOS device does not.
@EvanBacon My app is integrated into the existing app
. I'm purely creating a bundler
and combining thereact-native
with the existing app. How can bun
be used in this state?
NOTE : this is not expo
Any updates on this ?
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.