mirror of
https://github.com/status-im/react-native-config.git
synced 2025-02-28 06:50:47 +00:00
Introduce new approach for ios integration
* ruby scripts were refactored * environment values are now exposed to Info.plist via xcconfig file * environment values became available in Build Settings * cocoapods integration fixed - no need to add custom code to Podfile! (as suggested in this PR: https://github.com/luggit/react-native-config/pull/329)
This commit is contained in:
parent
7390b74934
commit
1eb6ac0199
49
README.md
49
README.md
@ -4,7 +4,6 @@ Module to expose config variables to your javascript code in React Native, suppo
|
||||
|
||||
Bring some [12 factor](http://12factor.net/config) love to your mobile apps!
|
||||
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Create a new file `.env` in the root of your React Native app:
|
||||
@ -17,15 +16,14 @@ GOOGLE_MAPS_API_KEY=abcdefgh
|
||||
Then access variables defined there from your app:
|
||||
|
||||
```js
|
||||
import Config from 'react-native-config'
|
||||
import Config from "react-native-config";
|
||||
|
||||
Config.API_URL // 'https://myapi.com'
|
||||
Config.GOOGLE_MAPS_API_KEY // 'abcdefgh'
|
||||
Config.API_URL; // 'https://myapi.com'
|
||||
Config.GOOGLE_MAPS_API_KEY; // 'abcdefgh'
|
||||
```
|
||||
|
||||
Keep in mind this module doesn't obfuscate or encrypt secrets for packaging, so **do not store sensitive keys in `.env`**. It's [basically impossible to prevent users from reverse engineering mobile app secrets](https://rammic.github.io/2015/07/28/hiding-secrets-in-android-apps/), so design your app (and APIs) with that in mind.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Install the package:
|
||||
@ -40,6 +38,11 @@ Link the library:
|
||||
$ react-native link react-native-config
|
||||
```
|
||||
|
||||
if cocoapods are used in the project then pod has to be installed as well:
|
||||
|
||||
```
|
||||
(cd ios; pod install)
|
||||
```
|
||||
|
||||
### Extra step for Android
|
||||
|
||||
@ -50,17 +53,6 @@ You'll also need to manually apply a plugin to your app, from `android/app/build
|
||||
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
|
||||
```
|
||||
|
||||
|
||||
### Extra step for iOS (support Info.plist)
|
||||
|
||||
* Go to your project -> Build Settings -> All
|
||||
* Search for "preprocess"
|
||||
* Set `Preprocess Info.plist File` to `Yes`
|
||||
* Set `Info.plist Preprocessor Prefix File` to `${BUILD_DIR}/GeneratedInfoPlistDotEnv.h`
|
||||
* Set `Info.plist Other Preprocessor Flags` to `-traditional`
|
||||
* If you don't see those settings, verify that "All" is selected at the top (instead of "Basic")
|
||||
|
||||
|
||||
#### Advanced Android Setup
|
||||
|
||||
In `android/app/build.gradle`, if you use `applicationIdSuffix` or `applicationId` that is different from the package name indicated in `AndroidManifest.xml` in `<manifest package="...">` tag, for example, to support different build variants:
|
||||
@ -125,14 +117,27 @@ NSString *apiUrl = [ReactNativeConfig envFor:@"API_URL"];
|
||||
NSDictionary *config = [ReactNativeConfig env];
|
||||
```
|
||||
|
||||
They're also available for configuration in `Info.plist`, by prepending `__RN_CONFIG_` to their name:
|
||||
#### Availability in Build settings and Info.plist
|
||||
|
||||
With one extra step environment values can be exposed to "Info.plist" and Build settings in the native project.
|
||||
|
||||
1. click on the file tree and create new file of type XCConfig
|
||||

|
||||

|
||||
2. save it under `ios` folder as "Config.xcconfig" with the following content:
|
||||
|
||||
```
|
||||
__RN_CONFIG_API_URL
|
||||
#include? "tmp.xcconfig"
|
||||
```
|
||||
|
||||
Note: Requires specific setup (see below) and a `Product > Clean` is required after changing the values to see the updated values.
|
||||
3. go to project settings
|
||||
4. apply config to your configurations
|
||||

|
||||
5. create new build phase for the scheme which will generate "tmp.xcconfig" before each build exposing values to Build Settings and Info.plist<sup>this has to be placed after custom env file explained [here](#ios-multi-scheme)</sup>:
|
||||
|
||||
```
|
||||
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
|
||||
```
|
||||
|
||||
### Different environments
|
||||
|
||||
@ -150,7 +155,6 @@ $ env:ENVFILE=".env.staging"; react-native run-ios # powershell
|
||||
|
||||
This also works for `run-android`. Alternatively, there are platform-specific options below.
|
||||
|
||||
|
||||
#### Android
|
||||
|
||||
The same environment variable can be used to assemble releases with a different config:
|
||||
@ -171,9 +175,10 @@ project.ext.envConfigFiles = [
|
||||
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
|
||||
```
|
||||
|
||||
|
||||
#### iOS
|
||||
|
||||
<a name="ios-multi-scheme"></a>
|
||||
|
||||
The basic idea in iOS is to have one scheme per environment file, so you can easily alternate between them.
|
||||
|
||||
Start by creating a new scheme:
|
||||
@ -200,7 +205,7 @@ This is still a bit experimental and dirty – let us know if you have a better
|
||||
When Proguard is enabled (which it is by default for Android release builds), it can rename the `BuildConfig` Java class in the minification process and prevent React Native Config from referencing it. To avoid this, add an exception to `android/app/proguard-rules.pro`:
|
||||
|
||||
-keep class com.mypackage.BuildConfig { *; }
|
||||
|
||||
|
||||
`mypackage` should match the `package` value in your `app/src/main/AndroidManifest.xml` file.
|
||||
|
||||
## Testing
|
||||
|
@ -57,9 +57,11 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3DF7F6AC203AA09B00D0EAB7 /* libReactNativeConfig-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactNativeConfig-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
50406729228CAD5A00E0438A /* ReadDotEnv.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = ReadDotEnv.rb; sourceTree = "<group>"; };
|
||||
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildXCConfig.rb; sourceTree = "<group>"; };
|
||||
EB2648DF1C7BE17A00B8F155 /* libReactNativeConfig.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeConfig.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeConfig.h; sourceTree = "<group>"; };
|
||||
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = BuildDotenvConfig.ruby; sourceTree = "<group>"; };
|
||||
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildDotenvConfig.rb; sourceTree = "<group>"; };
|
||||
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeConfig.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -101,9 +103,11 @@
|
||||
EB2648E11C7BE17A00B8F155 /* ReactNativeConfig */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */,
|
||||
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */,
|
||||
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */,
|
||||
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */,
|
||||
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */,
|
||||
50406729228CAD5A00E0438A /* ReadDotEnv.rb */,
|
||||
);
|
||||
path = ReactNativeConfig;
|
||||
sourceTree = "<group>";
|
||||
@ -172,6 +176,7 @@
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
);
|
||||
mainGroup = EB2648D61C7BE17A00B8F155;
|
||||
@ -192,13 +197,13 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby",
|
||||
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby;
|
||||
shellScript = "./ReactNativeConfig/BuildDotenvConfig.rb\n";
|
||||
};
|
||||
EBE4E8281C7BF689000F8875 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -206,13 +211,13 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby",
|
||||
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby;
|
||||
shellScript = "set -xe\necho \"$SRCROOT\"\n./ReactNativeConfig/BuildDotenvConfig.rb \"$SRCROOT/../../..\" \"$SRCROOT/ReactNativeConfig/\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
|
30
ios/ReactNativeConfig/BuildDotenvConfig.rb
Executable file
30
ios/ReactNativeConfig/BuildDotenvConfig.rb
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'ReadDotEnv'
|
||||
|
||||
envs_root = ARGV[0]
|
||||
m_output_path = ARGV[1]
|
||||
puts "reading env file from #{envs_root} and writing .m to #{m_output_path}"
|
||||
|
||||
# Allow utf-8 charactor in config value
|
||||
# For example, APP_NAME=中文字符
|
||||
Encoding.default_external = Encoding::UTF_8
|
||||
Encoding.default_internal = Encoding::UTF_8
|
||||
|
||||
dotenv, custom_env = read_dot_env(envs_root)
|
||||
puts "read dotenv #{dotenv}"
|
||||
|
||||
# create obj file that sets DOT_ENV as a NSDictionary
|
||||
dotenv_objc = dotenv.map { |k, v| %(@"#{k}":@"#{v.chomp}") }.join(',')
|
||||
template = <<EOF
|
||||
#define DOT_ENV @{ #{dotenv_objc} };
|
||||
EOF
|
||||
|
||||
# write it so that ReactNativeConfig.m can return it
|
||||
path = File.join(m_output_path, 'GeneratedDotEnv.m')
|
||||
File.open(path, 'w') { |f| f.puts template }
|
||||
|
||||
File.delete('/tmp/envfile') if custom_env
|
||||
|
||||
puts "Wrote to #{path}"
|
@ -1,79 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# Allow utf-8 charactor in config value
|
||||
# For example, APP_NAME=中文字符
|
||||
Encoding.default_external = Encoding::UTF_8
|
||||
Encoding.default_internal = Encoding::UTF_8
|
||||
|
||||
defaultEnvFile = ".env"
|
||||
|
||||
# pick a custom env file if set
|
||||
if File.exists?("/tmp/envfile")
|
||||
custom_env = true
|
||||
file = File.read("/tmp/envfile").strip
|
||||
else
|
||||
custom_env = false
|
||||
file = ENV["ENVFILE"] || defaultEnvFile
|
||||
end
|
||||
|
||||
puts "Reading env from #{file}"
|
||||
|
||||
dotenv = begin
|
||||
# https://regex101.com/r/cbm5Tp/1
|
||||
dotenv_pattern = /^(?:export\s+|)(?<key>[[:alnum:]_]+)=((?<quote>["'])?(?<val>.*?[^\\])\k<quote>?|)$/
|
||||
|
||||
# find that above node_modules/react-native-config/ios/
|
||||
path = File.join(Dir.pwd, "../../../#{file}")
|
||||
if File.exists?(path)
|
||||
raw = File.read(path)
|
||||
elsif File.exists?(file)
|
||||
raw = File.read(file)
|
||||
else
|
||||
defaultEnvPath = File.join(Dir.pwd, "../../../#{defaultEnvFile}")
|
||||
if !File.exists?(defaultEnvPath)
|
||||
# try as absolute path
|
||||
defaultEnvPath = defaultEnvFile
|
||||
end
|
||||
defaultRaw = File.read(defaultEnvPath)
|
||||
if (defaultRaw)
|
||||
raw = defaultRaw + "\n" + raw
|
||||
end
|
||||
end
|
||||
|
||||
raw.split("\n").inject({}) do |h, line|
|
||||
m = line.match(dotenv_pattern)
|
||||
next h if m.nil?
|
||||
key = m[:key]
|
||||
# Ensure string (in case of empty value) and escape any quotes present in the value.
|
||||
val = m[:val].to_s.gsub('"', '\"')
|
||||
h.merge(key => val)
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
puts("**************************")
|
||||
puts("*** Missing .env file ****")
|
||||
puts("**************************")
|
||||
{} # set dotenv as an empty hash
|
||||
end
|
||||
|
||||
# create obj file that sets DOT_ENV as a NSDictionary
|
||||
dotenv_objc = dotenv.map { |k, v| %Q(@"#{k}":@"#{v.chomp}") }.join(",")
|
||||
template = <<EOF
|
||||
#define DOT_ENV @{ #{dotenv_objc} };
|
||||
EOF
|
||||
|
||||
# write it so that ReactNativeConfig.m can return it
|
||||
path = File.join(ENV["SYMROOT"], "GeneratedDotEnv.m")
|
||||
File.open(path, "w") { |f| f.puts template }
|
||||
|
||||
# create header file with defines for the Info.plist preprocessor
|
||||
info_plist_defines_objc = dotenv.map { |k, v| %Q(#define __RN_CONFIG_#{k} #{v}) }.join("\n")
|
||||
|
||||
# write it so the Info.plist preprocessor can access it
|
||||
path = File.join(ENV["BUILD_DIR"], "GeneratedInfoPlistDotEnv.h")
|
||||
File.open(path, "w") { |f| f.puts info_plist_defines_objc }
|
||||
|
||||
if custom_env
|
||||
File.delete("/tmp/envfile")
|
||||
end
|
||||
|
||||
puts "Wrote to #{path}"
|
13
ios/ReactNativeConfig/BuildXCConfig.rb
Executable file
13
ios/ReactNativeConfig/BuildXCConfig.rb
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'ReadDotEnv'
|
||||
|
||||
envs_root = ARGV[0]
|
||||
config_output = ARGV[1]
|
||||
puts "reading env file from #{envs_root} and writing .config to #{config_output}"
|
||||
|
||||
dotenv, custom_env = read_dot_env(envs_root)
|
||||
|
||||
dotenv_xcconfig = dotenv.map { |k, v| %(#{k}=#{v}) }.join("\n")
|
||||
File.open(config_output, 'w') { |f| f.puts dotenv_xcconfig }
|
58
ios/ReactNativeConfig/ReadDotEnv.rb
Executable file
58
ios/ReactNativeConfig/ReadDotEnv.rb
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Allow utf-8 charactor in config value
|
||||
# For example, APP_NAME=中文字符
|
||||
Encoding.default_external = Encoding::UTF_8
|
||||
Encoding.default_internal = Encoding::UTF_8
|
||||
|
||||
# TODO: introduce a parameter which controls how to build relative path
|
||||
def read_dot_env(envs_root)
|
||||
defaultEnvFile = '.env'
|
||||
puts "going to read env file from root folder #{envs_root}"
|
||||
|
||||
# pick a custom env file if set
|
||||
if File.exist?('/tmp/envfile')
|
||||
custom_env = true
|
||||
file = File.read('/tmp/envfile').strip
|
||||
else
|
||||
custom_env = false
|
||||
file = ENV['ENVFILE'] || defaultEnvFile
|
||||
end
|
||||
|
||||
dotenv = begin
|
||||
# https://regex101.com/r/cbm5Tp/1
|
||||
dotenv_pattern = /^(?:export\s+|)(?<key>[[:alnum:]_]+)=((?<quote>["'])?(?<val>.*?[^\\])\k<quote>?|)$/
|
||||
|
||||
path = File.expand_path(File.join(envs_root, file.to_s))
|
||||
if File.exist?(path)
|
||||
raw = File.read(path)
|
||||
elsif File.exist?(file)
|
||||
raw = File.read(file)
|
||||
else
|
||||
defaultEnvPath = File.expand_path(File.join(envs_root, "../#{defaultEnvFile}"))
|
||||
unless File.exist?(defaultEnvPath)
|
||||
# try as absolute path
|
||||
defaultEnvPath = defaultEnvFile
|
||||
end
|
||||
defaultRaw = File.read(defaultEnvPath)
|
||||
raw = defaultRaw + "\n" + raw if defaultRaw
|
||||
end
|
||||
|
||||
raw.split("\n").inject({}) do |h, line|
|
||||
m = line.match(dotenv_pattern)
|
||||
next h if m.nil?
|
||||
|
||||
key = m[:key]
|
||||
# Ensure string (in case of empty value) and escape any quotes present in the value.
|
||||
val = m[:val].to_s.gsub('"', '\"')
|
||||
h.merge(key => val)
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
puts('**************************')
|
||||
puts('*** Missing .env file ****')
|
||||
puts('**************************')
|
||||
return [{}, false] # set dotenv as an empty hash
|
||||
end
|
||||
[dotenv, custom_env]
|
||||
end
|
@ -1,23 +1,35 @@
|
||||
require "json"
|
||||
# frozen_string_literal: true
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "react-native-config"
|
||||
s.version = package["version"]
|
||||
s.summary = "Expose config variables to React Native apps"
|
||||
s.author = "Pedro Belo"
|
||||
s.name = 'react-native-config'
|
||||
s.version = package['version']
|
||||
s.summary = 'Expose config variables to React Native apps'
|
||||
s.author = 'Pedro Belo'
|
||||
|
||||
s.homepage = "https://github.com/luggit/react-native-config"
|
||||
s.homepage = 'https://github.com/luggit/react-native-config'
|
||||
|
||||
s.license = "MIT"
|
||||
s.ios.deployment_target = "7.0"
|
||||
s.tvos.deployment_target = "9.0"
|
||||
s.license = 'MIT'
|
||||
s.ios.deployment_target = '7.0'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
|
||||
s.source = { :git => "https://github.com/luggit/react-native-config.git", :tag => "#{s.version}" }
|
||||
s.source = { git: 'https://github.com/luggit/react-native-config.git', tag: s.version.to_s }
|
||||
s.script_phase = {
|
||||
name: 'Config codegen',
|
||||
script: %(
|
||||
set -ex
|
||||
HOST_PATH="$SRCROOT/../.."
|
||||
"${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig"
|
||||
),
|
||||
execution_position: :before_compile,
|
||||
input_files: ['$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb']
|
||||
}
|
||||
|
||||
s.source_files = "ios/**/*.{h,m}"
|
||||
s.source_files = 'ios/**/*.{h,m}'
|
||||
s.requires_arc = true
|
||||
|
||||
s.dependency "React"
|
||||
s.dependency 'React'
|
||||
end
|
||||
|
BIN
readme-pics/1.ios_new_file.png
Normal file
BIN
readme-pics/1.ios_new_file.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 599 KiB |
BIN
readme-pics/2.ios_file_type.png
Normal file
BIN
readme-pics/2.ios_file_type.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 KiB |
BIN
readme-pics/3.ios_apply_config.png
Normal file
BIN
readme-pics/3.ios_apply_config.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 437 KiB |
Loading…
x
Reference in New Issue
Block a user