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:
Max Komarychev 2019-05-15 23:25:03 +03:00
parent 7390b74934
commit 1eb6ac0199
10 changed files with 164 additions and 120 deletions

View File

@ -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
![img](./readme-pics/1.ios_new_file.png)
![img](./readme-pics/2.ios_file_type.png)
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
![img](./readme-pics/3.ios_apply_config.png)
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

View File

@ -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 */

View 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}"

View File

@ -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}"

View 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 }

View 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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB