前言
在上一篇 native_modules.gradle分析 文章中,我们对三方库是如何依赖到Android项目有了一个大致的了解。
在 native_modules.gradle
的 getReactNativeConfig
方法中,执行了一个node命令,然后得到了非常关键的 reactNativeConfigOutput
后续的流程才能正常走下去。
String[] reactNativeConfigCommand = ["node", cliPath, "config"]
def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.root)
那么,reactNativeConfigOutput 这个数据到底是怎么获取到的呢?
下面我们来重点看下前端CLI的逻辑。
RN CLI
从 reactNativeConfigCommand 这个方法入手,本质上就是执行了 一个node命令,如下
node xxx/node_modules/@react-native-community/cli/build/bin.js config
我们可以直接在项目中执行下该命令,看看结果是什么。
{
"root": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis",
"reactNativePath": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/node_modules/react-native",
"reactNativeVersion": "0.73",
"dependencies": {
"react-native-device-info": {
"root": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/node_modules/react-native-device-info",
"name": "react-native-device-info",
"platforms": {
"ios": {
"podspecPath": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/node_modules/react-native-device-info/RNDeviceInfo.podspec",
"version": "10.12.0",
"configurations": [],
"scriptPhases": []
},
"android": {
"sourceDir": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/node_modules/react-native-device-info/android",
"packageImportPath": "import com.learnium.RNDeviceInfo.RNDeviceInfo;",
"packageInstance": "new RNDeviceInfo()",
"buildTypes": [],
"componentDescriptors": [],
"cmakeListsPath": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/node_modules/react-native-device-info/android/build/generated/source/codegen/jni/CMakeLists.txt"
}
}
}
},
"commands": [
{
"name": "log-ios",
"description": "starts iOS device syslog tail",
"options": [
{
"name": "--interactive",
"description": "Explicitly select simulator to tail logs from. By default it will tail logs from the first booted and available simulator."
}
]
},
{
"name": "run-ios",
"description": "builds your app and starts it on iOS simulator",
"examples": [
{
"desc": "Run on a different simulator, e.g. iPhone SE (2nd generation)",
"cmd": "npx react-native run-ios --simulator \"iPhone SE (2nd generation)\""
},
{
"desc": "Run on a connected device, e.g. Max's iPhone",
"cmd": "npx react-native run-ios --device \"Max's iPhone\""
},
{
"desc": "Run on the AppleTV simulator",
"cmd": "npx react-native run-ios --simulator \"Apple TV\" --scheme \"helloworld-tvOS\""
}
],
"options": [
{
"name": "--mode <string>",
"description": "Explicitly set the scheme configuration to use. This option is case sensitive."
},
{
"name": "--scheme <string>",
"description": "Explicitly set Xcode scheme to use"
},
{
"name": "--destination <string>",
"description": "Explicitly extend destination e.g. \"arch=x86_64\""
},
{
"name": "--verbose",
"description": "Do not use xcbeautify or xcpretty even if installed"
},
{
"name": "--xcconfig [string]",
"description": "Explicitly set xcconfig to use"
},
{
"name": "--buildFolder <string>",
"description": "Location for iOS build artifacts. Corresponds to Xcode's \"-derivedDataPath\"."
},
{
"name": "--extra-params <string>",
"description": "Custom params that will be passed to xcodebuild command."
},
{
"name": "--target <string>",
"description": "Explicitly set Xcode target to use."
},
{
"name": "--interactive",
"description": "Explicitly select which scheme and configuration to use before running a build"
},
{
"name": "--force-pods",
"description": "Force CocoaPods installation"
},
{
"name": "--no-packager",
"description": "Do not launch packager while running the app"
},
{
"name": "--port <number>",
"default": 8081
},
{
"name": "--terminal <string>",
"description": "Launches the Metro Bundler in a new window using the specified terminal path."
},
{
"name": "--binary-path <string>",
"description": "Path relative to project root where pre-built .app binary lives."
},
{
"name": "--list-devices",
"description": "List all available iOS devices and simulators and let you choose one to run the app. "
},
{
"name": "--simulator <string>",
"description": "Explicitly set the simulator to use. Optionally set the iOS version between parentheses at the end to match an exact version: \"iPhone 15 (17.0)\""
},
{
"name": "--device <string>",
"description": "Explicitly set the device to use by name. The value is not required if you have a single device connected."
},
{
"name": "--udid <string>",
"description": "Explicitly set the device to use by UDID"
}
]
},
{
"name": "build-ios",
"description": "builds your app for iOS platform",
"examples": [
{
"desc": "Build the app for all iOS devices in Release mode",
"cmd": "npx react-native build-ios --mode \"Release\""
}
],
"options": [
{
"name": "--mode <string>",
"description": "Explicitly set the scheme configuration to use. This option is case sensitive."
},
{
"name": "--scheme <string>",
"description": "Explicitly set Xcode scheme to use"
},
{
"name": "--destination <string>",
"description": "Explicitly extend destination e.g. \"arch=x86_64\""
},
{
"name": "--verbose",
"description": "Do not use xcbeautify or xcpretty even if installed"
},
{
"name": "--xcconfig [string]",
"description": "Explicitly set xcconfig to use"
},
{
"name": "--buildFolder <string>",
"description": "Location for iOS build artifacts. Corresponds to Xcode's \"-derivedDataPath\"."
},
{
"name": "--extra-params <string>",
"description": "Custom params that will be passed to xcodebuild command."
},
{
"name": "--target <string>",
"description": "Explicitly set Xcode target to use."
},
{
"name": "--interactive",
"description": "Explicitly select which scheme and configuration to use before running a build"
},
{
"name": "--force-pods",
"description": "Force CocoaPods installation"
}
]
},
{
"name": "log-android",
"description": "starts logkitty"
},
{
"name": "run-android",
"description": "builds your app and starts it on a connected Android emulator or device",
"options": [
{
"name": "--mode <string>",
"description": "Specify your app's build variant"
},
{
"name": "--tasks <list>",
"description": "Run custom Gradle tasks. By default it's \"assembleDebug\". Will override passed mode and variant arguments."
},
{
"name": "--active-arch-only",
"description": "Build native libraries only for the current device architecture for debug builds.",
"default": false
},
{
"name": "--extra-params <string>",
"description": "Custom params passed to gradle build command"
},
{
"name": "--interactive",
"description": "Explicitly select build type and flavour to use before running a build"
},
{
"name": "--no-packager",
"description": "Do not launch packager while running the app"
},
{
"name": "--port <number>",
"default": 8081
},
{
"name": "--terminal <string>",
"description": "Launches the Metro Bundler in a new window using the specified terminal path."
},
{
"name": "--appId <string>",
"description": "Specify an applicationId to launch after build. If not specified, `package` from AndroidManifest.xml will be used.",
"default": ""
},
{
"name": "--appIdSuffix <string>",
"description": "Specify an applicationIdSuffix to launch after build.",
"default": ""
},
{
"name": "--main-activity <string>",
"description": "Name of the activity to start"
},
{
"name": "--deviceId <string>",
"description": "builds your app and starts it on a specific device/simulator with the given device id (listed by running \"adb devices\" on the command line)."
},
{
"name": "--list-devices",
"description": "Lists all available Android devices and simulators and let you choose one to run the app",
"default": false
},
{
"name": "--binary-path <string>",
"description": "Path relative to project root where pre-built .apk binary lives."
},
{
"name": "--user <number>",
"description": "Id of the User Profile you want to install the app on."
}
]
},
{
"name": "build-android",
"description": "builds your app",
"options": [
{
"name": "--mode <string>",
"description": "Specify your app's build variant"
},
{
"name": "--tasks <list>",
"description": "Run custom Gradle tasks. By default it's \"assembleDebug\". Will override passed mode and variant arguments."
},
{
"name": "--active-arch-only",
"description": "Build native libraries only for the current device architecture for debug builds.",
"default": false
},
{
"name": "--extra-params <string>",
"description": "Custom params passed to gradle build command"
},
{
"name": "--interactive",
"description": "Explicitly select build type and flavour to use before running a build"
}
]
},
{
"name": "bundle",
"description": "Build the bundle for the provided JavaScript entry file.",
"options": [
{
"name": "--entry-file <path>",
"description": "Path to the root JS file, either absolute or relative to JS root"
},
{
"name": "--platform <string>",
"description": "Either \"ios\" or \"android\"",
"default": "ios"
},
{
"name": "--transformer <string>",
"description": "Specify a custom transformer to be used"
},
{
"name": "--dev [boolean]",
"description": "If false, warnings are disabled and the bundle is minified",
"default": true
},
{
"name": "--minify [boolean]",
"description": "Allows overriding whether bundle is minified. This defaults to false if dev is true, and true if dev is false. Disabling minification can be useful for speeding up production builds for testing purposes."
},
{
"name": "--bundle-output <string>",
"description": "File name where to store the resulting bundle, ex. /tmp/groups.bundle"
},
{
"name": "--bundle-encoding <string>",
"description": "Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).",
"default": "utf8"
},
{
"name": "--max-workers <number>",
"description": "Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine."
},
{
"name": "--sourcemap-output <string>",
"description": "File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map"
},
{
"name": "--sourcemap-sources-root <string>",
"description": "Path to make sourcemap's sources entries relative to, ex. /root/dir"
},
{
"name": "--sourcemap-use-absolute-path",
"description": "Report SourceMapURL using its full path",
"default": false
},
{
"name": "--assets-dest <string>",
"description": "Directory name where to store assets referenced in the bundle"
},
{
"name": "--unstable-transform-profile <string>",
"description": "Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default",
"default": "default"
},
{
"name": "--asset-catalog-dest [string]",
"description": "Path where to create an iOS Asset Catalog for images"
},
{
"name": "--reset-cache",
"description": "Removes cached files",
"default": false
},
{
"name": "--read-global-cache",
"description": "Try to fetch transformed JS code from the global cache, if configured.",
"default": false
},
{
"name": "--config <string>",
"description": "Path to the CLI configuration file"
}
]
},
{
"name": "ram-bundle",
"description": "Build the RAM bundle for the provided JavaScript entry file. See https://reactnative.dev/docs/ram-bundles-inline-requires.",
"options": [
{
"name": "--entry-file <path>",
"description": "Path to the root JS file, either absolute or relative to JS root"
},
{
"name": "--platform <string>",
"description": "Either \"ios\" or \"android\"",
"default": "ios"
},
{
"name": "--transformer <string>",
"description": "Specify a custom transformer to be used"
},
{
"name": "--dev [boolean]",
"description": "If false, warnings are disabled and the bundle is minified",
"default": true
},
{
"name": "--minify [boolean]",
"description": "Allows overriding whether bundle is minified. This defaults to false if dev is true, and true if dev is false. Disabling minification can be useful for speeding up production builds for testing purposes."
},
{
"name": "--bundle-output <string>",
"description": "File name where to store the resulting bundle, ex. /tmp/groups.bundle"
},
{
"name": "--bundle-encoding <string>",
"description": "Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).",
"default": "utf8"
},
{
"name": "--max-workers <number>",
"description": "Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine."
},
{
"name": "--sourcemap-output <string>",
"description": "File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map"
},
{
"name": "--sourcemap-sources-root <string>",
"description": "Path to make sourcemap's sources entries relative to, ex. /root/dir"
},
{
"name": "--sourcemap-use-absolute-path",
"description": "Report SourceMapURL using its full path",
"default": false
},
{
"name": "--assets-dest <string>",
"description": "Directory name where to store assets referenced in the bundle"
},
{
"name": "--unstable-transform-profile <string>",
"description": "Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default",
"default": "default"
},
{
"name": "--asset-catalog-dest [string]",
"description": "Path where to create an iOS Asset Catalog for images"
},
{
"name": "--reset-cache",
"description": "Removes cached files",
"default": false
},
{
"name": "--read-global-cache",
"description": "Try to fetch transformed JS code from the global cache, if configured.",
"default": false
},
{
"name": "--config <string>",
"description": "Path to the CLI configuration file"
},
{
"name": "--indexed-ram-bundle",
"description": "Force the \"Indexed RAM\" bundle file format, even when building for android",
"default": false
}
]
},
{
"name": "start",
"description": "Start the React Native development server.",
"options": [
{
"name": "--port <number>"
},
{
"name": "--host <string>",
"default": ""
},
{
"name": "--projectRoot <path>",
"description": "Path to a custom project root"
},
{
"name": "--watchFolders <list>",
"description": "Specify any additional folders to be added to the watch list"
},
{
"name": "--assetPlugins <list>",
"description": "Specify any additional asset plugins to be used by the packager by full filepath"
},
{
"name": "--sourceExts <list>",
"description": "Specify any additional source extensions to be used by the packager"
},
{
"name": "--max-workers <number>",
"description": "Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine."
},
{
"name": "--transformer <string>",
"description": "Specify a custom transformer to be used"
},
{
"name": "--reset-cache, --resetCache",
"description": "Removes cached files"
},
{
"name": "--custom-log-reporter-path, --customLogReporterPath <string>",
"description": "Path to a JavaScript file that exports a log reporter as a replacement for TerminalReporter"
},
{
"name": "--https",
"description": "Enables https connections to the server"
},
{
"name": "--key <path>",
"description": "Path to custom SSL key"
},
{
"name": "--cert <path>",
"description": "Path to custom SSL cert"
},
{
"name": "--config <string>",
"description": "Path to the CLI configuration file"
},
{
"name": "--no-interactive",
"description": "Disables interactive mode"
},
{
"name": "--experimental-debugger",
"description": "[Experimental] Enable the new debugger experience and 'j' to debug. This enables the new frontend experience only: connection reliability and some basic features are unstable in this release."
}
]
}
],
"healthChecks": [],
"platforms": {
"ios": {
},
"android": {
}
},
"project": {
"ios": {
"sourceDir": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/ios",
"xcodeProject": {
"name": "MyProject.xcodeproj",
"isWorkspace": false
}
},
"android": {
"sourceDir": "/Users/yuzhiqiang/workspace/RN/personal/RNProjectAnalysis/android",
"appName": "app",
"packageName": "com.yzq.rn_project_analysis",
"applicationId": "com.yzq.rn_project_analysis",
"mainActivity": ".MainActivity"
}
}
}
可以看到json数据中包含了
- reactNativePath
- reactNativeVersion
- dependencies
- commands
- healthChecks
- platforms
- project
这些关键信息,我们的脚本主要也是用到了这些信息,来自动添加依赖,生成PackageList.java文件等。
下面我们来详细看下这些关键信息是如何获取的。
入口在 xxx/node_modules/@react-native-community/cli/build/bin.js
文件中,我们先来看下这个文件的源码
if (_semver().default.satisfies(process.version, _cliDoctor().versionRanges.NODE_JS)) {
const {
run
} = require('./');
run();
} else {
console.error(`${
_chalk().default.red(`React Native needs Node.js ${
_cliDoctor().versionRanges.NODE_JS}. You're currently on version ${
process.version}. Please upgrade Node.js to a supported version and try again.`)}`);
}
bin.js的最终执行了一个当前目录下的index.js文件的一个run方法,这个run方法位于同级目录中index.js 中。
async function run(platformName) {
try {
await setupAndRun(platformName);
} catch (e) {
handleError(e);
}
}
随后调用了