oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
74.15k stars 2.77k forks source link

react-native support #123

Open matinzd opened 2 years ago

matinzd commented 2 years ago

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 commented 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

iamyellow commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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!

keeslinp commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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:

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](https://github.com/facebook/metro/blob/d36056d605a26559dd2aabab341682d53d7625e4/packages/metro-runtime/src/polyfills/require.js#L87-L90) 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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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

keeslinp commented 1 year ago

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 commented 1 year ago

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.

RayyanNafees commented 1 year ago

Seems like someone had success using bun with Expo

image

aashishsingla567 commented 1 year ago

Seems like someone had success using bun with Expo

image

Which version of bun ?

headfire94 commented 12 months ago

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 commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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 commented 8 months ago

@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

Nanome203 commented 1 month ago

Any updates on this ?