I am developing React Native application that includes different configurations for different possible clients, in a file such as src/config/config.js
. These configurations are quite complex. The file is structured based on the client name as key, and the values as the object entries, e.g.:
export default {
fooClient: {
apiUrl: "https://foo.example.com/",
barClient: {
apiUrl: "https://bar.example.com/"
}
}
Of course, there are many other option keys.
When building the app, I know for which client I want to do this, by specifying an Android build variant, e.g.:
ENVFILE=.env npx react-native run-android --variant fooDebug --appIdSuffix foo
For security reasons, I don't want keys of other clients to be included in the config file though. What are my options to remove all other client configs from this file before I build the app and ship it to a client?
I thought about the following: I modify the packager so that it strips out the keys that do not correspond to the current build variant.
I now have a transformer plugin for Metro that does the following:
const upstreamTransformer = require('metro-react-native-babel-transformer');
module.exports.transform = function(src, filename, options) {
if (typeof src === 'object') {
// handle RN >= 0.46
({ src, filename, options } = src);
}
if (filename.endsWith('config.js')) {
console.log('Transforming ' + filename);
let srcStripped = src.replace(';', '').replace('export default ', '');
let configObj = JSON.parse(srcStripped);
// TODO: get the build variant and strip all keys that we do not need from configObj
return upstreamTransformer.transform({
src: 'export default ' + JSON.stringify(configObj) + ';',
filename: filename,
options
});
} else {
return upstreamTransformer.transform({ src, filename, options });
}
};
But how do I know which build variant is being used?
If this seems like an XY problem, I am happy to explore alternatives to building the configuration dynamically. I cannot, however, use environment variables, since the configuration will be too complex for it to be just a list of .env
keys.
You shouldn't use Metro transform
this way. It's not clean and it may lead to missing configuration and/or damaged syntax sooner or later.
What I have done and suggest you, is to create 3 different configuration files under src/config/
; one file for fooClient.js
, one file for barClient.js
and one last file with common configuration client.js
. All files will export default configuration objects, but inside each fooClient
and barClient
, you'll use deepmerge
module to merge client.js
config:
client.js
:export default {
commonSettingA: "...",
commonSettings: {
...
}
...
}
fooClient.js
:import merge from 'deepmerge';
import config from './config';
export default merge.all([
config,
{
apiUrl: "https://foo.example.com/",
}
]);
barClient.js
:import merge from 'deepmerge';
import config from './config';
export default merge.all([
config,
{
apiUrl: "https://bar.example.com/",
}
]);
Then you can use an environment variable to pass the needed configuration and create a propriate metro resolve; @react-native-community/cli
does not pass command line arguments to metro config script. You can use process.argv
to parse it by yourself, but it's not worth it.
Here is how you can create a resolve inside metro.config.js
using environment variable:
const path = require("path");
module.exports = {
projectRoot: path.resolve(__dirname),
resolver: {
sourceExts: ['js', 'jsx', 'ts', 'tsx'],
extraNodeModules: {
// Local aliases
"@config": path.resolve(__dirname, "src/config", `${process.env.METRO_VARIANT}Client.js`)
}
}
};
Using this resolve, you'll have to import the configuration like this:
import config from '@config';
Then you add 2 package.json
scripts, one for fooClient
and one for barClient
:
{
...
"scripts": {
"run:android:foo": "METRO_VARIANT=foo ENVFILE=.env npx react-native run-android --variant fooDebug --appIdSuffix foo",
"run:android:bar": "METRO_VARIANT=bar ENVFILE=.env npx react-native run-android --variant barDebug --appIdSuffix bar",
...
}
...
}
Then you just run the needed script:
yarn run:android:foo # will build with fooClient.js
yarn run:android:bar # will build with barClient.js