We want to know how React Native will be bundling the JavaScript code and assets during the build of iOS or Android native apps.
Updated on June 2024.
In the Xcode project, there’s a “Bundle React Native code and images” build phase, which will execute something like the following:
`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"`
The react-native-xcode.sh
lives in the react-native
package, located at packages/react-native/scripts/react-native-xcode.sh
inside the react-native repo.
It executes the bundle command of React Native CLI (CLI entry point at packages/react-native/cli.js
), and Hermes to emit the binary from the bundled JS file.
Check the Shared section to continue on what will happen afterwards.
In the android/app/build.gradle
file, there’s something like the following:
apply plugin: "com.facebook.react"
We can do that because in the android/build.gradle
file, there’s something like the following:
buildscript {
// ...
dependencies {
// ...
classpath('com.facebook.react:react-native-gradle-plugin')
}
}
And also, this in settings.gradle
:
includeBuild('../node_modules/@react-native/gradle-plugin')
(Reference: https://reactnative.dev/docs/0.74/integration-with-existing-apps#configuring-gradle, see the “Configuring Gradle” section under Android)
That React Native Gradle Plugin is located at packages/react-native-gradle-plugin
in the react-native
repo.
TODO: I don’t know how the createBundleReleaseJsAndAssets
task (listed in ./gradlew tasks --all
) is being called during build. Seems that the createBundle${targetName}JsAndAssets
task is defined here.
In BundleHermesCTask.kt
it will run the bundle command, where the cliFile
is passed from TaskConfiguration.kt
and determined here in PathUtils.kt
, which will normally be react-native/cli.js
.
It also runs the Hermes command to emit a binary, if Hermes is enabled.
Check the Shared section to continue on what will happen afterwards.
Things start from react-native/cli.js
, where it just forwards stuff to @react-native-community/cli
- entry point is the run
function in index.ts.
The available commands (such as run-ios
, build-ios
, run-android
, etc.), including the bundle
command that we care about, is loaded via the loadConfig
function, which located in @react-native-community/cli-config.
The loadConfig
starts its work by reading user config (react-native.config.js
, or .ts
) from disk, then merge it with dependencies.
The findDependencies
function is used to get a list of dependencies to consider, it does it’s work by listing all dependencies
and devDependencies
in the RN project’s package.json
. The result will be an array like this: ['react', 'react-native', '@babel/core', '@babel/preset-env', '@babel/runtime', '@react-native/babel-preset', '@react-native/eslint-config', '@react-native/metro-config', ...]
.
For each dependency, readDependencyConfigFromDisk
is used to load the dependency config, under the hood cosmiconfig
is used to search for react-native.config.js
, or .ts
inside the package. For example, there’s a react-native.config.js
file under the react-native package, and there’s where the bundle
commend is defined.
Note that if there’re duplicate commands defined, the removeDuplicateCommands
function will be used to remove them.
To find the bundle
command, check react-native/react-native.config.js
 and we’ll find out that it’s from the @react-native/community-cli-plugin
package, where the source is located under packages/community-cli-plugin
inside the react-native repo.
So the bundle command lives here.
Inside it, bundleImpl
defaults to metroBundle
.
bundleImpl.build
, which is metroBundle.build
, is actually packagerClient.build
, where packagerClient
is the server
.
And server
is const server = new Server(config)
, so the code for bundling is here.
TODO: explore internals.
TODO